diff options
Diffstat (limited to 'sound/core')
95 files changed, 43314 insertions, 0 deletions
diff --git a/sound/core/Kconfig b/sound/core/Kconfig new file mode 100644 index 000000000000..d1e800b9866d --- /dev/null +++ b/sound/core/Kconfig @@ -0,0 +1,133 @@ +# ALSA soundcard-configuration +config SND_TIMER + tristate + depends on SND + +config SND_PCM + tristate + select SND_TIMER + depends on SND + +config SND_HWDEP + tristate + depends on SND + +config SND_RAWMIDI + tristate + depends on SND + +config SND_SEQUENCER + tristate "Sequencer support" + depends on SND + select SND_TIMER + help + Say Y or M to enable MIDI sequencer and router support. This + feature allows routing and enqueueing of MIDI events. Events + can be processed at a given time. + + Many programs require this feature, so you should enable it + unless you know what you're doing. + +config SND_SEQ_DUMMY + tristate "Sequencer dummy client" + depends on SND_SEQUENCER + help + Say Y here to enable the dummy sequencer client. This client + is a simple MIDI-through client: all normal input events are + redirected to the output port immediately. + + You don't need this unless you want to connect many MIDI + devices or applications together. + + To compile this driver as a module, choose M here: the module + will be called snd-seq-dummy. + +config SND_OSSEMUL + bool + depends on SND + +config SND_MIXER_OSS + tristate "OSS Mixer API" + depends on SND + select SND_OSSEMUL + help + To enable OSS mixer API emulation (/dev/mixer*), say Y here + and read <file:Documentation/sound/alsa/OSS-Emulation.txt>. + + Many programs still use the OSS API, so say Y. + + To compile this driver as a module, choose M here: the module + will be called snd-mixer-oss. + +config SND_PCM_OSS + tristate "OSS PCM (digital audio) API" + depends on SND + select SND_OSSEMUL + select SND_PCM + help + To enable OSS digital audio (PCM) emulation (/dev/dsp*), say Y + here and read <file:Documentation/sound/alsa/OSS-Emulation.txt>. + + Many programs still use the OSS API, so say Y. + + To compile this driver as a module, choose M here: the module + will be called snd-pcm-oss. + +config SND_SEQUENCER_OSS + bool "OSS Sequencer API" + depends on SND && SND_SEQUENCER + select SND_OSSEMUL + help + Say Y here to enable OSS sequencer emulation (both + /dev/sequencer and /dev/music interfaces). + + Many programs still use the OSS API, so say Y. + + To compile this driver as a module, choose M here: the module + will be called snd-seq-oss. + +config SND_RTCTIMER + tristate "RTC Timer support" + depends on SND && RTC + select SND_TIMER + help + Say Y here to enable RTC timer support for ALSA. ALSA uses + the RTC timer as a precise timing source and maps the RTC + timer to ALSA's timer interface. The ALSA sequencer code also + can use this timing source. + + To compile this driver as a module, choose M here: the module + will be called snd-rtctimer. + +config SND_VERBOSE_PRINTK + bool "Verbose printk" + depends on SND + help + Say Y here to enable verbose log messages. These messages + will help to identify source file and position containing + printed messages. + + You don't need this unless you're debugging ALSA. + +config SND_DEBUG + bool "Debug" + depends on SND + help + Say Y here to enable ALSA debug code. + +config SND_DEBUG_MEMORY + bool "Debug memory" + depends on SND_DEBUG + help + Say Y here to enable debugging of memory allocations. + +config SND_DEBUG_DETECT + bool "Debug detection" + depends on SND_DEBUG + help + Say Y here to enable extra-verbose log messages printed when + detecting devices. + +config SND_GENERIC_PM + bool + depends on SND diff --git a/sound/core/Makefile b/sound/core/Makefile new file mode 100644 index 000000000000..764ac184b223 --- /dev/null +++ b/sound/core/Makefile @@ -0,0 +1,33 @@ +# +# Makefile for ALSA +# Copyright (c) 1999,2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-objs := sound.o init.o memory.o info.o control.o misc.o \ + device.o wrappers.o +ifeq ($(CONFIG_ISA),y) +snd-objs += isadma.o +endif +ifeq ($(CONFIG_SND_OSSEMUL),y) +snd-objs += sound_oss.o info_oss.o +endif + +snd-pcm-objs := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \ + pcm_memory.o + +snd-page-alloc-objs := memalloc.o sgbuf.o + +snd-rawmidi-objs := rawmidi.o +snd-timer-objs := timer.o +snd-rtctimer-objs := rtctimer.o +snd-hwdep-objs := hwdep.o + +obj-$(CONFIG_SND) += snd.o +obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o +obj-$(CONFIG_SND_TIMER) += snd-timer.o +obj-$(CONFIG_SND_RTCTIMER) += snd-rtctimer.o +obj-$(CONFIG_SND_PCM) += snd-pcm.o snd-page-alloc.o +obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o + +obj-$(CONFIG_SND_OSSEMUL) += oss/ +obj-$(CONFIG_SND_SEQUENCER) += seq/ diff --git a/sound/core/control.c b/sound/core/control.c new file mode 100644 index 000000000000..f4ea6bff1dd3 --- /dev/null +++ b/sound/core/control.c @@ -0,0 +1,1375 @@ +/* + * Routines for driver control interface + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/threads.h> +#include <linux/interrupt.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <sound/control.h> + +/* max number of user-defined controls */ +#define MAX_USER_CONTROLS 32 + +typedef struct _snd_kctl_ioctl { + struct list_head list; /* list of all ioctls */ + snd_kctl_ioctl_func_t fioctl; +} snd_kctl_ioctl_t; + +#define snd_kctl_ioctl(n) list_entry(n, snd_kctl_ioctl_t, list) + +static DECLARE_RWSEM(snd_ioctl_rwsem); +static LIST_HEAD(snd_control_ioctls); +#ifdef CONFIG_COMPAT +static LIST_HEAD(snd_control_compat_ioctls); +#endif + +static int snd_ctl_open(struct inode *inode, struct file *file) +{ + int cardnum = SNDRV_MINOR_CARD(iminor(inode)); + unsigned long flags; + snd_card_t *card; + snd_ctl_file_t *ctl; + int err; + + card = snd_cards[cardnum]; + if (!card) { + err = -ENODEV; + goto __error1; + } + err = snd_card_file_add(card, file); + if (err < 0) { + err = -ENODEV; + goto __error1; + } + if (!try_module_get(card->module)) { + err = -EFAULT; + goto __error2; + } + ctl = kcalloc(1, sizeof(*ctl), GFP_KERNEL); + if (ctl == NULL) { + err = -ENOMEM; + goto __error; + } + INIT_LIST_HEAD(&ctl->events); + init_waitqueue_head(&ctl->change_sleep); + spin_lock_init(&ctl->read_lock); + ctl->card = card; + ctl->pid = current->pid; + file->private_data = ctl; + write_lock_irqsave(&card->ctl_files_rwlock, flags); + list_add_tail(&ctl->list, &card->ctl_files); + write_unlock_irqrestore(&card->ctl_files_rwlock, flags); + return 0; + + __error: + module_put(card->module); + __error2: + snd_card_file_remove(card, file); + __error1: + return err; +} + +static void snd_ctl_empty_read_queue(snd_ctl_file_t * ctl) +{ + snd_kctl_event_t *cread; + + spin_lock(&ctl->read_lock); + while (!list_empty(&ctl->events)) { + cread = snd_kctl_event(ctl->events.next); + list_del(&cread->list); + kfree(cread); + } + spin_unlock(&ctl->read_lock); +} + +static int snd_ctl_release(struct inode *inode, struct file *file) +{ + unsigned long flags; + struct list_head *list; + snd_card_t *card; + snd_ctl_file_t *ctl; + snd_kcontrol_t *control; + unsigned int idx; + + ctl = file->private_data; + fasync_helper(-1, file, 0, &ctl->fasync); + file->private_data = NULL; + card = ctl->card; + write_lock_irqsave(&card->ctl_files_rwlock, flags); + list_del(&ctl->list); + write_unlock_irqrestore(&card->ctl_files_rwlock, flags); + down_write(&card->controls_rwsem); + list_for_each(list, &card->controls) { + control = snd_kcontrol(list); + for (idx = 0; idx < control->count; idx++) + if (control->vd[idx].owner == ctl) + control->vd[idx].owner = NULL; + } + up_write(&card->controls_rwsem); + snd_ctl_empty_read_queue(ctl); + kfree(ctl); + module_put(card->module); + snd_card_file_remove(card, file); + return 0; +} + +void snd_ctl_notify(snd_card_t *card, unsigned int mask, snd_ctl_elem_id_t *id) +{ + unsigned long flags; + struct list_head *flist; + snd_ctl_file_t *ctl; + snd_kctl_event_t *ev; + + snd_runtime_check(card != NULL && id != NULL, return); + read_lock(&card->ctl_files_rwlock); +#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) + card->mixer_oss_change_count++; +#endif + list_for_each(flist, &card->ctl_files) { + struct list_head *elist; + ctl = snd_ctl_file(flist); + if (!ctl->subscribed) + continue; + spin_lock_irqsave(&ctl->read_lock, flags); + list_for_each(elist, &ctl->events) { + ev = snd_kctl_event(elist); + if (ev->id.numid == id->numid) { + ev->mask |= mask; + goto _found; + } + } + ev = kcalloc(1, sizeof(*ev), GFP_ATOMIC); + if (ev) { + ev->id = *id; + ev->mask = mask; + list_add_tail(&ev->list, &ctl->events); + } else { + snd_printk(KERN_ERR "No memory available to allocate event\n"); + } + _found: + wake_up(&ctl->change_sleep); + spin_unlock_irqrestore(&ctl->read_lock, flags); + kill_fasync(&ctl->fasync, SIGIO, POLL_IN); + } + read_unlock(&card->ctl_files_rwlock); +} + +/** + * snd_ctl_new - create a control instance from the template + * @control: the control template + * @access: the default control access + * + * Allocates a new snd_kcontrol_t instance and copies the given template + * to the new instance. It does not copy volatile data (access). + * + * Returns the pointer of the new instance, or NULL on failure. + */ +snd_kcontrol_t *snd_ctl_new(snd_kcontrol_t * control, unsigned int access) +{ + snd_kcontrol_t *kctl; + unsigned int idx; + + snd_runtime_check(control != NULL, return NULL); + snd_runtime_check(control->count > 0, return NULL); + kctl = kcalloc(1, sizeof(*kctl) + sizeof(snd_kcontrol_volatile_t) * control->count, GFP_KERNEL); + if (kctl == NULL) + return NULL; + *kctl = *control; + for (idx = 0; idx < kctl->count; idx++) + kctl->vd[idx].access = access; + return kctl; +} + +/** + * snd_ctl_new1 - create a control instance from the template + * @ncontrol: the initialization record + * @private_data: the private data to set + * + * Allocates a new snd_kcontrol_t instance and initialize from the given + * template. When the access field of ncontrol is 0, it's assumed as + * READWRITE access. When the count field is 0, it's assumes as one. + * + * Returns the pointer of the newly generated instance, or NULL on failure. + */ +snd_kcontrol_t *snd_ctl_new1(snd_kcontrol_new_t * ncontrol, void *private_data) +{ + snd_kcontrol_t kctl; + unsigned int access; + + snd_runtime_check(ncontrol != NULL, return NULL); + snd_assert(ncontrol->info != NULL, return NULL); + memset(&kctl, 0, sizeof(kctl)); + kctl.id.iface = ncontrol->iface; + kctl.id.device = ncontrol->device; + kctl.id.subdevice = ncontrol->subdevice; + if (ncontrol->name) + strlcpy(kctl.id.name, ncontrol->name, sizeof(kctl.id.name)); + kctl.id.index = ncontrol->index; + kctl.count = ncontrol->count ? ncontrol->count : 1; + access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : + (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE| + SNDRV_CTL_ELEM_ACCESS_DINDIRECT|SNDRV_CTL_ELEM_ACCESS_INDIRECT)); + kctl.info = ncontrol->info; + kctl.get = ncontrol->get; + kctl.put = ncontrol->put; + kctl.private_value = ncontrol->private_value; + kctl.private_data = private_data; + return snd_ctl_new(&kctl, access); +} + +/** + * snd_ctl_free_one - release the control instance + * @kcontrol: the control instance + * + * Releases the control instance created via snd_ctl_new() + * or snd_ctl_new1(). + * Don't call this after the control was added to the card. + */ +void snd_ctl_free_one(snd_kcontrol_t * kcontrol) +{ + if (kcontrol) { + if (kcontrol->private_free) + kcontrol->private_free(kcontrol); + kfree(kcontrol); + } +} + +static unsigned int snd_ctl_hole_check(snd_card_t * card, + unsigned int count) +{ + struct list_head *list; + snd_kcontrol_t *kctl; + + list_for_each(list, &card->controls) { + kctl = snd_kcontrol(list); + if ((kctl->id.numid <= card->last_numid && + kctl->id.numid + kctl->count > card->last_numid) || + (kctl->id.numid <= card->last_numid + count - 1 && + kctl->id.numid + kctl->count > card->last_numid + count - 1)) + return card->last_numid = kctl->id.numid + kctl->count - 1; + } + return card->last_numid; +} + +static int snd_ctl_find_hole(snd_card_t * card, unsigned int count) +{ + unsigned int last_numid, iter = 100000; + + last_numid = card->last_numid; + while (last_numid != snd_ctl_hole_check(card, count)) { + if (--iter == 0) { + /* this situation is very unlikely */ + snd_printk(KERN_ERR "unable to allocate new control numid\n"); + return -ENOMEM; + } + last_numid = card->last_numid; + } + return 0; +} + +/** + * snd_ctl_add - add the control instance to the card + * @card: the card instance + * @kcontrol: the control instance to add + * + * Adds the control instance created via snd_ctl_new() or + * snd_ctl_new1() to the given card. Assigns also an unique + * numid used for fast search. + * + * Returns zero if successful, or a negative error code on failure. + * + * It frees automatically the control which cannot be added. + */ +int snd_ctl_add(snd_card_t * card, snd_kcontrol_t * kcontrol) +{ + snd_ctl_elem_id_t id; + unsigned int idx; + + snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL); + snd_assert(kcontrol->info != NULL, return -EINVAL); + id = kcontrol->id; + down_write(&card->controls_rwsem); + if (snd_ctl_find_id(card, &id)) { + up_write(&card->controls_rwsem); + snd_ctl_free_one(kcontrol); + snd_printd(KERN_ERR "control %i:%i:%i:%s:%i is already present\n", + id.iface, + id.device, + id.subdevice, + id.name, + id.index); + return -EBUSY; + } + if (snd_ctl_find_hole(card, kcontrol->count) < 0) { + up_write(&card->controls_rwsem); + snd_ctl_free_one(kcontrol); + return -ENOMEM; + } + list_add_tail(&kcontrol->list, &card->controls); + card->controls_count += kcontrol->count; + kcontrol->id.numid = card->last_numid + 1; + card->last_numid += kcontrol->count; + up_write(&card->controls_rwsem); + for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id); + return 0; +} + +/** + * snd_ctl_remove - remove the control from the card and release it + * @card: the card instance + * @kcontrol: the control instance to remove + * + * Removes the control from the card and then releases the instance. + * You don't need to call snd_ctl_free_one(). You must be in + * the write lock - down_write(&card->controls_rwsem). + * + * Returns 0 if successful, or a negative error code on failure. + */ +int snd_ctl_remove(snd_card_t * card, snd_kcontrol_t * kcontrol) +{ + snd_ctl_elem_id_t id; + unsigned int idx; + + snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL); + list_del(&kcontrol->list); + card->controls_count -= kcontrol->count; + id = kcontrol->id; + for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_REMOVE, &id); + snd_ctl_free_one(kcontrol); + return 0; +} + +/** + * snd_ctl_remove_id - remove the control of the given id and release it + * @card: the card instance + * @id: the control id to remove + * + * Finds the control instance with the given id, removes it from the + * card list and releases it. + * + * Returns 0 if successful, or a negative error code on failure. + */ +int snd_ctl_remove_id(snd_card_t * card, snd_ctl_elem_id_t *id) +{ + snd_kcontrol_t *kctl; + int ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + ret = snd_ctl_remove(card, kctl); + up_write(&card->controls_rwsem); + return ret; +} + +/** + * snd_ctl_remove_unlocked_id - remove the unlocked control of the given id and release it + * @file: active control handle + * @id: the control id to remove + * + * Finds the control instance with the given id, removes it from the + * card list and releases it. + * + * Returns 0 if successful, or a negative error code on failure. + */ +static int snd_ctl_remove_unlocked_id(snd_ctl_file_t * file, snd_ctl_elem_id_t *id) +{ + snd_card_t *card = file->card; + snd_kcontrol_t *kctl; + int idx, ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + for (idx = 0; idx < kctl->count; idx++) + if (kctl->vd[idx].owner != NULL && kctl->vd[idx].owner != file) { + up_write(&card->controls_rwsem); + return -EBUSY; + } + ret = snd_ctl_remove(card, kctl); + up_write(&card->controls_rwsem); + return ret; +} + +/** + * snd_ctl_rename_id - replace the id of a control on the card + * @card: the card instance + * @src_id: the old id + * @dst_id: the new id + * + * Finds the control with the old id from the card, and replaces the + * id with the new one. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_ctl_rename_id(snd_card_t * card, snd_ctl_elem_id_t *src_id, snd_ctl_elem_id_t *dst_id) +{ + snd_kcontrol_t *kctl; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, src_id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + kctl->id = *dst_id; + kctl->id.numid = card->last_numid + 1; + card->last_numid += kctl->count; + up_write(&card->controls_rwsem); + return 0; +} + +/** + * snd_ctl_find_numid - find the control instance with the given number-id + * @card: the card instance + * @numid: the number-id to search + * + * Finds the control instance with the given number-id from the card. + * + * Returns the pointer of the instance if found, or NULL if not. + * + * The caller must down card->controls_rwsem before calling this function + * (if the race condition can happen). + */ +snd_kcontrol_t *snd_ctl_find_numid(snd_card_t * card, unsigned int numid) +{ + struct list_head *list; + snd_kcontrol_t *kctl; + + snd_runtime_check(card != NULL && numid != 0, return NULL); + list_for_each(list, &card->controls) { + kctl = snd_kcontrol(list); + if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid) + return kctl; + } + return NULL; +} + +/** + * snd_ctl_find_id - find the control instance with the given id + * @card: the card instance + * @id: the id to search + * + * Finds the control instance with the given id from the card. + * + * Returns the pointer of the instance if found, or NULL if not. + * + * The caller must down card->controls_rwsem before calling this function + * (if the race condition can happen). + */ +snd_kcontrol_t *snd_ctl_find_id(snd_card_t * card, snd_ctl_elem_id_t *id) +{ + struct list_head *list; + snd_kcontrol_t *kctl; + + snd_runtime_check(card != NULL && id != NULL, return NULL); + if (id->numid != 0) + return snd_ctl_find_numid(card, id->numid); + list_for_each(list, &card->controls) { + kctl = snd_kcontrol(list); + if (kctl->id.iface != id->iface) + continue; + if (kctl->id.device != id->device) + continue; + if (kctl->id.subdevice != id->subdevice) + continue; + if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name))) + continue; + if (kctl->id.index > id->index) + continue; + if (kctl->id.index + kctl->count <= id->index) + continue; + return kctl; + } + return NULL; +} + +static int snd_ctl_card_info(snd_card_t * card, snd_ctl_file_t * ctl, + unsigned int cmd, void __user *arg) +{ + snd_ctl_card_info_t *info; + + info = kcalloc(1, sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + down_read(&snd_ioctl_rwsem); + info->card = card->number; + strlcpy(info->id, card->id, sizeof(info->id)); + strlcpy(info->driver, card->driver, sizeof(info->driver)); + strlcpy(info->name, card->shortname, sizeof(info->name)); + strlcpy(info->longname, card->longname, sizeof(info->longname)); + strlcpy(info->mixername, card->mixername, sizeof(info->mixername)); + strlcpy(info->components, card->components, sizeof(info->components)); + up_read(&snd_ioctl_rwsem); + if (copy_to_user(arg, info, sizeof(snd_ctl_card_info_t))) { + kfree(info); + return -EFAULT; + } + kfree(info); + return 0; +} + +static int snd_ctl_elem_list(snd_card_t *card, snd_ctl_elem_list_t __user *_list) +{ + struct list_head *plist; + snd_ctl_elem_list_t list; + snd_kcontrol_t *kctl; + snd_ctl_elem_id_t *dst, *id; + unsigned int offset, space, first, jidx; + + if (copy_from_user(&list, _list, sizeof(list))) + return -EFAULT; + offset = list.offset; + space = list.space; + first = 0; + /* try limit maximum space */ + if (space > 16384) + return -ENOMEM; + if (space > 0) { + /* allocate temporary buffer for atomic operation */ + dst = vmalloc(space * sizeof(snd_ctl_elem_id_t)); + if (dst == NULL) + return -ENOMEM; + down_read(&card->controls_rwsem); + list.count = card->controls_count; + plist = card->controls.next; + while (plist != &card->controls) { + if (offset == 0) + break; + kctl = snd_kcontrol(plist); + if (offset < kctl->count) + break; + offset -= kctl->count; + plist = plist->next; + } + list.used = 0; + id = dst; + while (space > 0 && plist != &card->controls) { + kctl = snd_kcontrol(plist); + for (jidx = offset; space > 0 && jidx < kctl->count; jidx++) { + snd_ctl_build_ioff(id, kctl, jidx); + id++; + space--; + list.used++; + } + plist = plist->next; + offset = 0; + } + up_read(&card->controls_rwsem); + if (list.used > 0 && copy_to_user(list.pids, dst, list.used * sizeof(snd_ctl_elem_id_t))) { + vfree(dst); + return -EFAULT; + } + vfree(dst); + } else { + down_read(&card->controls_rwsem); + list.count = card->controls_count; + up_read(&card->controls_rwsem); + } + if (copy_to_user(_list, &list, sizeof(list))) + return -EFAULT; + return 0; +} + +static int snd_ctl_elem_info(snd_ctl_file_t *ctl, snd_ctl_elem_info_t *info) +{ + snd_card_t *card = ctl->card; + snd_kcontrol_t *kctl; + snd_kcontrol_volatile_t *vd; + unsigned int index_offset; + int result; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &info->id); + if (kctl == NULL) { + up_read(&card->controls_rwsem); + return -ENOENT; + } +#ifdef CONFIG_SND_DEBUG + info->access = 0; +#endif + result = kctl->info(kctl, info); + if (result >= 0) { + snd_assert(info->access == 0, ); + index_offset = snd_ctl_get_ioff(kctl, &info->id); + vd = &kctl->vd[index_offset]; + snd_ctl_build_ioff(&info->id, kctl, index_offset); + info->access = vd->access; + if (vd->owner) { + info->access |= SNDRV_CTL_ELEM_ACCESS_LOCK; + if (vd->owner == ctl) + info->access |= SNDRV_CTL_ELEM_ACCESS_OWNER; + info->owner = vd->owner_pid; + } else { + info->owner = -1; + } + } + up_read(&card->controls_rwsem); + return result; +} + +static int snd_ctl_elem_info_user(snd_ctl_file_t *ctl, snd_ctl_elem_info_t __user *_info) +{ + snd_ctl_elem_info_t info; + int result; + + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + result = snd_ctl_elem_info(ctl, &info); + if (result >= 0) + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return result; +} + +int snd_ctl_elem_read(snd_card_t *card, snd_ctl_elem_value_t *control) +{ + snd_kcontrol_t *kctl; + snd_kcontrol_volatile_t *vd; + unsigned int index_offset; + int result, indirect; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &control->id); + if (kctl == NULL) { + result = -ENOENT; + } else { + index_offset = snd_ctl_get_ioff(kctl, &control->id); + vd = &kctl->vd[index_offset]; + indirect = vd->access & SNDRV_CTL_ELEM_ACCESS_INDIRECT ? 1 : 0; + if (control->indirect != indirect) { + result = -EACCES; + } else { + if ((vd->access & SNDRV_CTL_ELEM_ACCESS_READ) && kctl->get != NULL) { + snd_ctl_build_ioff(&control->id, kctl, index_offset); + result = kctl->get(kctl, control); + } else { + result = -EPERM; + } + } + } + up_read(&card->controls_rwsem); + return result; +} + +static int snd_ctl_elem_read_user(snd_card_t *card, snd_ctl_elem_value_t __user *_control) +{ + snd_ctl_elem_value_t *control; + int result; + + control = kmalloc(sizeof(*control), GFP_KERNEL); + if (control == NULL) + return -ENOMEM; + if (copy_from_user(control, _control, sizeof(*control))) { + kfree(control); + return -EFAULT; + } + result = snd_ctl_elem_read(card, control); + if (result >= 0) + if (copy_to_user(_control, control, sizeof(*control))) + result = -EFAULT; + kfree(control); + return result; +} + +int snd_ctl_elem_write(snd_card_t *card, snd_ctl_file_t *file, snd_ctl_elem_value_t *control) +{ + snd_kcontrol_t *kctl; + snd_kcontrol_volatile_t *vd; + unsigned int index_offset; + int result, indirect; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &control->id); + if (kctl == NULL) { + result = -ENOENT; + } else { + index_offset = snd_ctl_get_ioff(kctl, &control->id); + vd = &kctl->vd[index_offset]; + indirect = vd->access & SNDRV_CTL_ELEM_ACCESS_INDIRECT ? 1 : 0; + if (control->indirect != indirect) { + result = -EACCES; + } else { + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) || + kctl->put == NULL || + (file && vd->owner != NULL && vd->owner != file)) { + result = -EPERM; + } else { + snd_ctl_build_ioff(&control->id, kctl, index_offset); + result = kctl->put(kctl, control); + } + if (result > 0) { + up_read(&card->controls_rwsem); + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &control->id); + return 0; + } + } + } + up_read(&card->controls_rwsem); + return result; +} + +static int snd_ctl_elem_write_user(snd_ctl_file_t *file, snd_ctl_elem_value_t __user *_control) +{ + snd_ctl_elem_value_t *control; + int result; + + control = kmalloc(sizeof(*control), GFP_KERNEL); + if (control == NULL) + return -ENOMEM; + if (copy_from_user(control, _control, sizeof(*control))) { + kfree(control); + return -EFAULT; + } + result = snd_ctl_elem_write(file->card, file, control); + if (result >= 0) + if (copy_to_user(_control, control, sizeof(*control))) + result = -EFAULT; + kfree(control); + return result; +} + +static int snd_ctl_elem_lock(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id) +{ + snd_card_t *card = file->card; + snd_ctl_elem_id_t id; + snd_kcontrol_t *kctl; + snd_kcontrol_volatile_t *vd; + int result; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &id); + if (kctl == NULL) { + result = -ENOENT; + } else { + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + if (vd->owner != NULL) + result = -EBUSY; + else { + vd->owner = file; + vd->owner_pid = current->pid; + result = 0; + } + } + up_write(&card->controls_rwsem); + return result; +} + +static int snd_ctl_elem_unlock(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id) +{ + snd_card_t *card = file->card; + snd_ctl_elem_id_t id; + snd_kcontrol_t *kctl; + snd_kcontrol_volatile_t *vd; + int result; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &id); + if (kctl == NULL) { + result = -ENOENT; + } else { + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + if (vd->owner == NULL) + result = -EINVAL; + else if (vd->owner != file) + result = -EPERM; + else { + vd->owner = NULL; + vd->owner_pid = 0; + result = 0; + } + } + up_write(&card->controls_rwsem); + return result; +} + +struct user_element { + snd_ctl_elem_info_t info; + void *elem_data; /* element data */ + unsigned long elem_data_size; /* size of element data in bytes */ + void *priv_data; /* private data (like strings for enumerated type) */ + unsigned long priv_data_size; /* size of private data in bytes */ +}; + +static int snd_ctl_elem_user_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo) +{ + struct user_element *ue = kcontrol->private_data; + + *uinfo = ue->info; + return 0; +} + +static int snd_ctl_elem_user_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + struct user_element *ue = kcontrol->private_data; + + memcpy(&ucontrol->value, ue->elem_data, ue->elem_data_size); + return 0; +} + +static int snd_ctl_elem_user_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + int change; + struct user_element *ue = kcontrol->private_data; + + change = memcmp(&ucontrol->value, ue->elem_data, ue->elem_data_size) != 0; + if (change) + memcpy(ue->elem_data, &ucontrol->value, ue->elem_data_size); + return change; +} + +static void snd_ctl_elem_user_free(snd_kcontrol_t * kcontrol) +{ + kfree(kcontrol->private_data); +} + +static int snd_ctl_elem_add(snd_ctl_file_t *file, snd_ctl_elem_info_t *info, int replace) +{ + snd_card_t *card = file->card; + snd_kcontrol_t kctl, *_kctl; + unsigned int access; + long private_size; + struct user_element *ue; + int idx, err; + + if (card->user_ctl_count >= MAX_USER_CONTROLS) + return -ENOMEM; + if (info->count > 1024) + return -EINVAL; + access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE : + (info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE)); + info->id.numid = 0; + memset(&kctl, 0, sizeof(kctl)); + down_write(&card->controls_rwsem); + _kctl = snd_ctl_find_id(card, &info->id); + err = 0; + if (_kctl) { + if (replace) + err = snd_ctl_remove(card, _kctl); + else + err = -EBUSY; + } else { + if (replace) + err = -ENOENT; + } + up_write(&card->controls_rwsem); + if (err < 0) + return err; + memcpy(&kctl.id, &info->id, sizeof(info->id)); + kctl.count = info->owner ? info->owner : 1; + access |= SNDRV_CTL_ELEM_ACCESS_USER; + kctl.info = snd_ctl_elem_user_info; + if (access & SNDRV_CTL_ELEM_ACCESS_READ) + kctl.get = snd_ctl_elem_user_get; + if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) + kctl.put = snd_ctl_elem_user_put; + switch (info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + private_size = sizeof(char); + if (info->count > 128) + return -EINVAL; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER: + private_size = sizeof(long); + if (info->count > 128) + return -EINVAL; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + private_size = sizeof(long long); + if (info->count > 64) + return -EINVAL; + break; + case SNDRV_CTL_ELEM_TYPE_BYTES: + private_size = sizeof(unsigned char); + if (info->count > 512) + return -EINVAL; + break; + case SNDRV_CTL_ELEM_TYPE_IEC958: + private_size = sizeof(struct sndrv_aes_iec958); + if (info->count != 1) + return -EINVAL; + break; + default: + return -EINVAL; + } + private_size *= info->count; + ue = kcalloc(1, sizeof(struct user_element) + private_size, GFP_KERNEL); + if (ue == NULL) + return -ENOMEM; + ue->info = *info; + ue->elem_data = (char *)ue + sizeof(*ue); + ue->elem_data_size = private_size; + kctl.private_free = snd_ctl_elem_user_free; + _kctl = snd_ctl_new(&kctl, access); + if (_kctl == NULL) { + kfree(_kctl->private_data); + return -ENOMEM; + } + _kctl->private_data = ue; + for (idx = 0; idx < _kctl->count; idx++) + _kctl->vd[idx].owner = file; + err = snd_ctl_add(card, _kctl); + if (err < 0) { + snd_ctl_free_one(_kctl); + return err; + } + + down_write(&card->controls_rwsem); + card->user_ctl_count++; + up_write(&card->controls_rwsem); + + return 0; +} + +static int snd_ctl_elem_add_user(snd_ctl_file_t *file, snd_ctl_elem_info_t __user *_info, int replace) +{ + snd_ctl_elem_info_t info; + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + return snd_ctl_elem_add(file, &info, replace); +} + +static int snd_ctl_elem_remove(snd_ctl_file_t *file, snd_ctl_elem_id_t __user *_id) +{ + snd_ctl_elem_id_t id; + int err; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + err = snd_ctl_remove_unlocked_id(file, &id); + if (! err) { + snd_card_t *card = file->card; + down_write(&card->controls_rwsem); + card->user_ctl_count--; + up_write(&card->controls_rwsem); + } + return err; +} + +static int snd_ctl_subscribe_events(snd_ctl_file_t *file, int __user *ptr) +{ + int subscribe; + if (get_user(subscribe, ptr)) + return -EFAULT; + if (subscribe < 0) { + subscribe = file->subscribed; + if (put_user(subscribe, ptr)) + return -EFAULT; + return 0; + } + if (subscribe) { + file->subscribed = 1; + return 0; + } else if (file->subscribed) { + snd_ctl_empty_read_queue(file); + file->subscribed = 0; + } + return 0; +} + +#ifdef CONFIG_PM +/* + * change the power state + */ +static int snd_ctl_set_power_state(snd_card_t *card, unsigned int power_state) +{ + switch (power_state) { + case SNDRV_CTL_POWER_D0: + if (card->power_state != power_state) { + card->pm_resume(card); + snd_power_change_state(card, power_state); + } + break; + case SNDRV_CTL_POWER_D3hot: + if (card->power_state != power_state) { + card->pm_suspend(card, PMSG_SUSPEND); + snd_power_change_state(card, power_state); + } + break; + case SNDRV_CTL_POWER_D1: + case SNDRV_CTL_POWER_D2: + case SNDRV_CTL_POWER_D3cold: + /* not supported yet */ + default: + return -EINVAL; + } + return 0; +} +#endif + +static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_ctl_file_t *ctl; + snd_card_t *card; + struct list_head *list; + snd_kctl_ioctl_t *p; + void __user *argp = (void __user *)arg; + int __user *ip = argp; + int err; + + ctl = file->private_data; + card = ctl->card; + snd_assert(card != NULL, return -ENXIO); + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0; + case SNDRV_CTL_IOCTL_CARD_INFO: + return snd_ctl_card_info(card, ctl, cmd, argp); + case SNDRV_CTL_IOCTL_ELEM_LIST: + return snd_ctl_elem_list(ctl->card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO: + return snd_ctl_elem_info_user(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_READ: + return snd_ctl_elem_read_user(ctl->card, argp); + case SNDRV_CTL_IOCTL_ELEM_WRITE: + return snd_ctl_elem_write_user(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_LOCK: + return snd_ctl_elem_lock(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_UNLOCK: + return snd_ctl_elem_unlock(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_ADD: + return snd_ctl_elem_add_user(ctl, argp, 0); + case SNDRV_CTL_IOCTL_ELEM_REPLACE: + return snd_ctl_elem_add_user(ctl, argp, 1); + case SNDRV_CTL_IOCTL_ELEM_REMOVE: + return snd_ctl_elem_remove(ctl, argp); + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + return snd_ctl_subscribe_events(ctl, ip); + case SNDRV_CTL_IOCTL_POWER: + if (get_user(err, ip)) + return -EFAULT; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; +#ifdef CONFIG_PM + if (card->pm_suspend && card->pm_resume) { + snd_power_lock(card); + err = snd_ctl_set_power_state(card, err); + snd_power_unlock(card); + } else +#endif + err = -ENOPROTOOPT; + return err; + case SNDRV_CTL_IOCTL_POWER_STATE: +#ifdef CONFIG_PM + return put_user(card->power_state, ip) ? -EFAULT : 0; +#else + return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0; +#endif + } + down_read(&snd_ioctl_rwsem); + list_for_each(list, &snd_control_ioctls) { + p = list_entry(list, snd_kctl_ioctl_t, list); + err = p->fioctl(card, ctl, cmd, arg); + if (err != -ENOIOCTLCMD) { + up_read(&snd_ioctl_rwsem); + return err; + } + } + up_read(&snd_ioctl_rwsem); + snd_printd("unknown ioctl = 0x%x\n", cmd); + return -ENOTTY; +} + +static ssize_t snd_ctl_read(struct file *file, char __user *buffer, size_t count, loff_t * offset) +{ + snd_ctl_file_t *ctl; + int err = 0; + ssize_t result = 0; + + ctl = file->private_data; + snd_assert(ctl != NULL && ctl->card != NULL, return -ENXIO); + if (!ctl->subscribed) + return -EBADFD; + if (count < sizeof(snd_ctl_event_t)) + return -EINVAL; + spin_lock_irq(&ctl->read_lock); + while (count >= sizeof(snd_ctl_event_t)) { + snd_ctl_event_t ev; + snd_kctl_event_t *kev; + while (list_empty(&ctl->events)) { + wait_queue_t wait; + if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { + err = -EAGAIN; + goto __end_lock; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&ctl->change_sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&ctl->read_lock); + schedule(); + remove_wait_queue(&ctl->change_sleep, &wait); + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + spin_lock_irq(&ctl->read_lock); + } + kev = snd_kctl_event(ctl->events.next); + ev.type = SNDRV_CTL_EVENT_ELEM; + ev.data.elem.mask = kev->mask; + ev.data.elem.id = kev->id; + list_del(&kev->list); + spin_unlock_irq(&ctl->read_lock); + kfree(kev); + if (copy_to_user(buffer, &ev, sizeof(snd_ctl_event_t))) { + err = -EFAULT; + goto __end; + } + spin_lock_irq(&ctl->read_lock); + buffer += sizeof(snd_ctl_event_t); + count -= sizeof(snd_ctl_event_t); + result += sizeof(snd_ctl_event_t); + } + __end_lock: + spin_unlock_irq(&ctl->read_lock); + __end: + return result > 0 ? result : err; +} + +static unsigned int snd_ctl_poll(struct file *file, poll_table * wait) +{ + unsigned int mask; + snd_ctl_file_t *ctl; + + ctl = file->private_data; + if (!ctl->subscribed) + return 0; + poll_wait(file, &ctl->change_sleep, wait); + + mask = 0; + if (!list_empty(&ctl->events)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +/* + * register the device-specific control-ioctls. + * called from each device manager like pcm.c, hwdep.c, etc. + */ +static int _snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists) +{ + snd_kctl_ioctl_t *pn; + + pn = kcalloc(1, sizeof(snd_kctl_ioctl_t), GFP_KERNEL); + if (pn == NULL) + return -ENOMEM; + pn->fioctl = fcn; + down_write(&snd_ioctl_rwsem); + list_add_tail(&pn->list, lists); + up_write(&snd_ioctl_rwsem); + return 0; +} + +int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_register_ioctl(fcn, &snd_control_ioctls); +} + +#ifdef CONFIG_COMPAT +int snd_ctl_register_ioctl_compat(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_register_ioctl(fcn, &snd_control_compat_ioctls); +} +#endif + +/* + * de-register the device-specific control-ioctls. + */ +static int _snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists) +{ + struct list_head *list; + snd_kctl_ioctl_t *p; + + snd_runtime_check(fcn != NULL, return -EINVAL); + down_write(&snd_ioctl_rwsem); + list_for_each(list, lists) { + p = list_entry(list, snd_kctl_ioctl_t, list); + if (p->fioctl == fcn) { + list_del(&p->list); + up_write(&snd_ioctl_rwsem); + kfree(p); + return 0; + } + } + up_write(&snd_ioctl_rwsem); + snd_BUG(); + return -EINVAL; +} + +int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_unregister_ioctl(fcn, &snd_control_ioctls); +} + +#ifdef CONFIG_COMPAT +int snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_unregister_ioctl(fcn, &snd_control_compat_ioctls); +} + +#endif + +static int snd_ctl_fasync(int fd, struct file * file, int on) +{ + snd_ctl_file_t *ctl; + int err; + ctl = file->private_data; + err = fasync_helper(fd, file, on, &ctl->fasync); + if (err < 0) + return err; + return 0; +} + +/* + * ioctl32 compat + */ +#ifdef CONFIG_COMPAT +#include "control_compat.c" +#else +#define snd_ctl_ioctl_compat NULL +#endif + +/* + * INIT PART + */ + +static struct file_operations snd_ctl_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_ctl_read, + .open = snd_ctl_open, + .release = snd_ctl_release, + .poll = snd_ctl_poll, + .unlocked_ioctl = snd_ctl_ioctl, + .compat_ioctl = snd_ctl_ioctl_compat, + .fasync = snd_ctl_fasync, +}; + +static snd_minor_t snd_ctl_reg = +{ + .comment = "ctl", + .f_ops = &snd_ctl_f_ops, +}; + +/* + * registration of the control device + */ +static int snd_ctl_dev_register(snd_device_t *device) +{ + snd_card_t *card = device->device_data; + int err, cardnum; + char name[16]; + + snd_assert(card != NULL, return -ENXIO); + cardnum = card->number; + snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO); + sprintf(name, "controlC%i", cardnum); + if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, + card, 0, &snd_ctl_reg, name)) < 0) + return err; + return 0; +} + +/* + * disconnection of the control device + */ +static int snd_ctl_dev_disconnect(snd_device_t *device) +{ + snd_card_t *card = device->device_data; + struct list_head *flist; + snd_ctl_file_t *ctl; + + down_read(&card->controls_rwsem); + list_for_each(flist, &card->ctl_files) { + ctl = snd_ctl_file(flist); + wake_up(&ctl->change_sleep); + kill_fasync(&ctl->fasync, SIGIO, POLL_ERR); + } + up_read(&card->controls_rwsem); + return 0; +} + +/* + * free all controls + */ +static int snd_ctl_dev_free(snd_device_t *device) +{ + snd_card_t *card = device->device_data; + snd_kcontrol_t *control; + + down_write(&card->controls_rwsem); + while (!list_empty(&card->controls)) { + control = snd_kcontrol(card->controls.next); + snd_ctl_remove(card, control); + } + up_write(&card->controls_rwsem); + return 0; +} + +/* + * de-registration of the control device + */ +static int snd_ctl_dev_unregister(snd_device_t *device) +{ + snd_card_t *card = device->device_data; + int err, cardnum; + + snd_assert(card != NULL, return -ENXIO); + cardnum = card->number; + snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO); + if ((err = snd_unregister_device(SNDRV_DEVICE_TYPE_CONTROL, card, 0)) < 0) + return err; + return snd_ctl_dev_free(device); +} + +/* + * create control core: + * called from init.c + */ +int snd_ctl_create(snd_card_t *card) +{ + static snd_device_ops_t ops = { + .dev_free = snd_ctl_dev_free, + .dev_register = snd_ctl_dev_register, + .dev_disconnect = snd_ctl_dev_disconnect, + .dev_unregister = snd_ctl_dev_unregister + }; + + snd_assert(card != NULL, return -ENXIO); + return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); +} diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c new file mode 100644 index 000000000000..7fdabea4bfc8 --- /dev/null +++ b/sound/core/control_compat.c @@ -0,0 +1,412 @@ +/* + * compat ioctls for control API + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* this file included from control.c */ + +#include <linux/compat.h> + +struct sndrv_ctl_elem_list32 { + u32 offset; + u32 space; + u32 used; + u32 count; + u32 pids; + unsigned char reserved[50]; +} /* don't set packed attribute here */; + +static int snd_ctl_elem_list_compat(snd_card_t *card, struct sndrv_ctl_elem_list32 __user *data32) +{ + struct sndrv_ctl_elem_list __user *data; + compat_caddr_t ptr; + int err; + + data = compat_alloc_user_space(sizeof(*data)); + + /* offset, space, used, count */ + if (copy_in_user(data, data32, 4 * sizeof(u32))) + return -EFAULT; + /* pids */ + if (get_user(ptr, &data32->pids) || + put_user(compat_ptr(ptr), &data->pids)) + return -EFAULT; + err = snd_ctl_elem_list(card, data); + if (err < 0) + return err; + /* copy the result */ + if (copy_in_user(data32, data, 4 * sizeof(u32))) + return -EFAULT; + return 0; +} + +/* + * control element info + * it uses union, so the things are not easy.. + */ + +struct sndrv_ctl_elem_info32 { + struct sndrv_ctl_elem_id id; // the size of struct is same + s32 type; + u32 access; + u32 count; + s32 owner; + union { + struct { + s32 min; + s32 max; + s32 step; + } integer; + struct { + u64 min; + u64 max; + u64 step; + } integer64; + struct { + u32 items; + u32 item; + char name[64]; + } enumerated; + unsigned char reserved[128]; + } value; + unsigned char reserved[64]; +} __attribute__((packed)); + +static int snd_ctl_elem_info_compat(snd_ctl_file_t *ctl, struct sndrv_ctl_elem_info32 __user *data32) +{ + struct sndrv_ctl_elem_info *data; + int err; + + data = kcalloc(1, sizeof(*data), GFP_KERNEL); + if (! data) + return -ENOMEM; + + err = -EFAULT; + /* copy id */ + if (copy_from_user(&data->id, &data32->id, sizeof(data->id))) + goto error; + /* we need to copy the item index. + * hope this doesn't break anything.. + */ + if (get_user(data->value.enumerated.item, &data32->value.enumerated.item)) + goto error; + err = snd_ctl_elem_info(ctl, data); + if (err < 0) + goto error; + /* restore info to 32bit */ + err = -EFAULT; + /* id, type, access, count */ + if (copy_to_user(&data32->id, &data->id, sizeof(data->id)) || + copy_to_user(&data32->type, &data->type, 3 * sizeof(u32))) + goto error; + if (put_user(data->owner, &data32->owner)) + goto error; + switch (data->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + if (put_user(data->value.integer.min, &data32->value.integer.min) || + put_user(data->value.integer.max, &data32->value.integer.max) || + put_user(data->value.integer.step, &data32->value.integer.step)) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + if (copy_to_user(&data32->value.integer64, + &data->value.integer64, + sizeof(data->value.integer64))) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + if (copy_to_user(&data32->value.enumerated, + &data->value.enumerated, + sizeof(data->value.enumerated))) + goto error; + break; + default: + break; + } + err = 0; + error: + kfree(data); + return err; +} + +/* read / write */ +struct sndrv_ctl_elem_value32 { + struct sndrv_ctl_elem_id id; + unsigned int indirect; /* bit-field causes misalignment */ + union { + s32 integer[128]; + unsigned char data[512]; +#ifndef CONFIG_X86_64 + s64 integer64[64]; +#endif + } value; + unsigned char reserved[128]; +}; + + +/* get the value type and count of the control */ +static int get_ctl_type(snd_card_t *card, snd_ctl_elem_id_t *id, int *countp) +{ + snd_kcontrol_t *kctl; + snd_ctl_elem_info_t info; + int err; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, id); + if (! kctl) { + up_read(&card->controls_rwsem); + return -ENXIO; + } + info.id = *id; + err = kctl->info(kctl, &info); + up_read(&card->controls_rwsem); + if (err >= 0) { + err = info.type; + *countp = info.count; + } + return err; +} + +static int get_elem_size(int type, int count) +{ + switch (type) { + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + return sizeof(s64) * count; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + return sizeof(int) * count; + case SNDRV_CTL_ELEM_TYPE_BYTES: + return 512; + case SNDRV_CTL_ELEM_TYPE_IEC958: + return sizeof(struct sndrv_aes_iec958); + default: + return -1; + } +} + +static int copy_ctl_value_from_user(snd_card_t *card, + struct sndrv_ctl_elem_value *data, + struct sndrv_ctl_elem_value32 __user *data32, + int *typep, int *countp) +{ + int i, type, count, size; + unsigned int indirect; + + if (copy_from_user(&data->id, &data32->id, sizeof(data->id))) + return -EFAULT; + if (get_user(indirect, &data32->indirect)) + return -EFAULT; + if (indirect) + return -EINVAL; + type = get_ctl_type(card, &data->id, &count); + if (type < 0) + return type; + + if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + int val; + if (get_user(val, &data32->value.integer[i])) + return -EFAULT; + data->value.integer.value[i] = val; + } + } else { + size = get_elem_size(type, count); + if (size < 0) { + printk(KERN_ERR "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); + return -EINVAL; + } + if (copy_from_user(data->value.bytes.data, + data32->value.data, size)) + return -EFAULT; + } + + *typep = type; + *countp = count; + return 0; +} + +/* restore the value to 32bit */ +static int copy_ctl_value_to_user(struct sndrv_ctl_elem_value32 __user *data32, + struct sndrv_ctl_elem_value *data, + int type, int count) +{ + int i, size; + + if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + int val; + val = data->value.integer.value[i]; + if (put_user(val, &data32->value.integer[i])) + return -EFAULT; + } + } else { + size = get_elem_size(type, count); + if (copy_to_user(data32->value.data, + data->value.bytes.data, size)) + return -EFAULT; + } + return 0; +} + +static int snd_ctl_elem_read_user_compat(snd_card_t *card, + struct sndrv_ctl_elem_value32 __user *data32) +{ + struct sndrv_ctl_elem_value *data; + int err, type, count; + + data = kcalloc(1, sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0) + goto error; + if ((err = snd_ctl_elem_read(card, data)) < 0) + goto error; + err = copy_ctl_value_to_user(data32, data, type, count); + error: + kfree(data); + return err; +} + +static int snd_ctl_elem_write_user_compat(snd_ctl_file_t *file, + struct sndrv_ctl_elem_value32 __user *data32) +{ + struct sndrv_ctl_elem_value *data; + int err, type, count; + + data = kcalloc(1, sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + if ((err = copy_ctl_value_from_user(file->card, data, data32, &type, &count)) < 0) + goto error; + if ((err = snd_ctl_elem_write(file->card, file, data)) < 0) + goto error; + err = copy_ctl_value_to_user(data32, data, type, count); + error: + kfree(data); + return err; +} + +/* add or replace a user control */ +static int snd_ctl_elem_add_compat(snd_ctl_file_t *file, + struct sndrv_ctl_elem_info32 __user *data32, + int replace) +{ + struct sndrv_ctl_elem_info *data; + int err; + + data = kcalloc(1, sizeof(*data), GFP_KERNEL); + if (! data) + return -ENOMEM; + + err = -EFAULT; + /* id, type, access, count */ \ + if (copy_from_user(&data->id, &data32->id, sizeof(data->id)) || + copy_from_user(&data->type, &data32->type, 3 * sizeof(u32))) + goto error; + if (get_user(data->owner, &data32->owner) || + get_user(data->type, &data32->type)) + goto error; + switch (data->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + if (get_user(data->value.integer.min, &data32->value.integer.min) || + get_user(data->value.integer.max, &data32->value.integer.max) || + get_user(data->value.integer.step, &data32->value.integer.step)) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + if (copy_from_user(&data->value.integer64, + &data32->value.integer64, + sizeof(data->value.integer64))) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + if (copy_from_user(&data->value.enumerated, + &data32->value.enumerated, + sizeof(data->value.enumerated))) + goto error; + break; + default: + break; + } + err = snd_ctl_elem_add(file, data, replace); + error: + kfree(data); + return err; +} + +enum { + SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct sndrv_ctl_elem_list32), + SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct sndrv_ctl_elem_info32), + SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct sndrv_ctl_elem_value32), + SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct sndrv_ctl_elem_value32), + SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct sndrv_ctl_elem_info32), + SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct sndrv_ctl_elem_info32), +}; + +static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_ctl_file_t *ctl; + struct list_head *list; + void __user *argp = compat_ptr(arg); + int err; + + ctl = file->private_data; + snd_assert(ctl && ctl->card, return -ENXIO); + + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + case SNDRV_CTL_IOCTL_CARD_INFO: + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + case SNDRV_CTL_IOCTL_POWER: + case SNDRV_CTL_IOCTL_POWER_STATE: + case SNDRV_CTL_IOCTL_ELEM_LOCK: + case SNDRV_CTL_IOCTL_ELEM_UNLOCK: + return snd_ctl_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_CTL_IOCTL_ELEM_LIST32: + return snd_ctl_elem_list_compat(ctl->card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO32: + return snd_ctl_elem_info_compat(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_READ32: + return snd_ctl_elem_read_user_compat(ctl->card, argp); + case SNDRV_CTL_IOCTL_ELEM_WRITE32: + return snd_ctl_elem_write_user_compat(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_ADD32: + return snd_ctl_elem_add_compat(ctl, argp, 0); + case SNDRV_CTL_IOCTL_ELEM_REPLACE32: + return snd_ctl_elem_add_compat(ctl, argp, 1); + } + + down_read(&snd_ioctl_rwsem); + list_for_each(list, &snd_control_compat_ioctls) { + snd_kctl_ioctl_t *p = list_entry(list, snd_kctl_ioctl_t, list); + if (p->fioctl) { + err = p->fioctl(ctl->card, ctl, cmd, arg); + if (err != -ENOIOCTLCMD) { + up_read(&snd_ioctl_rwsem); + return err; + } + } + } + up_read(&snd_ioctl_rwsem); + return -ENOIOCTLCMD; +} diff --git a/sound/core/device.c b/sound/core/device.c new file mode 100644 index 000000000000..18c71f913d2a --- /dev/null +++ b/sound/core/device.c @@ -0,0 +1,240 @@ +/* + * Device management routines + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/errno.h> +#include <sound/core.h> + +/** + * snd_device_new - create an ALSA device component + * @card: the card instance + * @type: the device type, SNDRV_DEV_TYPE_XXX + * @device_data: the data pointer of this device + * @ops: the operator table + * + * Creates a new device component for the given data pointer. + * The device will be assigned to the card and managed together + * by the card. + * + * The data pointer plays a role as the identifier, too, so the + * pointer address must be unique and unchanged. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_device_new(snd_card_t *card, snd_device_type_t type, + void *device_data, snd_device_ops_t *ops) +{ + snd_device_t *dev; + + snd_assert(card != NULL && device_data != NULL && ops != NULL, return -ENXIO); + dev = kcalloc(1, sizeof(*dev), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + dev->card = card; + dev->type = type; + dev->state = SNDRV_DEV_BUILD; + dev->device_data = device_data; + dev->ops = ops; + list_add(&dev->list, &card->devices); /* add to the head of list */ + return 0; +} + +/** + * snd_device_free - release the device from the card + * @card: the card instance + * @device_data: the data pointer to release + * + * Removes the device from the list on the card and invokes the + * callback, dev_unregister or dev_free, corresponding to the state. + * Then release the device. + * + * Returns zero if successful, or a negative error code on failure or if the + * device not found. + */ +int snd_device_free(snd_card_t *card, void *device_data) +{ + struct list_head *list; + snd_device_t *dev; + + snd_assert(card != NULL, return -ENXIO); + snd_assert(device_data != NULL, return -ENXIO); + list_for_each(list, &card->devices) { + dev = snd_device(list); + if (dev->device_data != device_data) + continue; + /* unlink */ + list_del(&dev->list); + if ((dev->state == SNDRV_DEV_REGISTERED || dev->state == SNDRV_DEV_DISCONNECTED) && + dev->ops->dev_unregister) { + if (dev->ops->dev_unregister(dev)) + snd_printk(KERN_ERR "device unregister failure\n"); + } else { + if (dev->ops->dev_free) { + if (dev->ops->dev_free(dev)) + snd_printk(KERN_ERR "device free failure\n"); + } + } + kfree(dev); + return 0; + } + snd_printd("device free %p (from %p), not found\n", device_data, __builtin_return_address(0)); + return -ENXIO; +} + +/** + * snd_device_free - disconnect the device + * @card: the card instance + * @device_data: the data pointer to disconnect + * + * Turns the device into the disconnection state, invoking + * dev_disconnect callback, if the device was already registered. + * + * Usually called from snd_card_disconnect(). + * + * Returns zero if successful, or a negative error code on failure or if the + * device not found. + */ +int snd_device_disconnect(snd_card_t *card, void *device_data) +{ + struct list_head *list; + snd_device_t *dev; + + snd_assert(card != NULL, return -ENXIO); + snd_assert(device_data != NULL, return -ENXIO); + list_for_each(list, &card->devices) { + dev = snd_device(list); + if (dev->device_data != device_data) + continue; + if (dev->state == SNDRV_DEV_REGISTERED && dev->ops->dev_disconnect) { + if (dev->ops->dev_disconnect(dev)) + snd_printk(KERN_ERR "device disconnect failure\n"); + dev->state = SNDRV_DEV_DISCONNECTED; + } + return 0; + } + snd_printd("device disconnect %p (from %p), not found\n", device_data, __builtin_return_address(0)); + return -ENXIO; +} + +/** + * snd_device_register - register the device + * @card: the card instance + * @device_data: the data pointer to register + * + * Registers the device which was already created via + * snd_device_new(). Usually this is called from snd_card_register(), + * but it can be called later if any new devices are created after + * invocation of snd_card_register(). + * + * Returns zero if successful, or a negative error code on failure or if the + * device not found. + */ +int snd_device_register(snd_card_t *card, void *device_data) +{ + struct list_head *list; + snd_device_t *dev; + int err; + + snd_assert(card != NULL && device_data != NULL, return -ENXIO); + list_for_each(list, &card->devices) { + dev = snd_device(list); + if (dev->device_data != device_data) + continue; + if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) { + if ((err = dev->ops->dev_register(dev)) < 0) + return err; + dev->state = SNDRV_DEV_REGISTERED; + return 0; + } + return -EBUSY; + } + snd_BUG(); + return -ENXIO; +} + +/* + * register all the devices on the card. + * called from init.c + */ +int snd_device_register_all(snd_card_t *card) +{ + struct list_head *list; + snd_device_t *dev; + int err; + + snd_assert(card != NULL, return -ENXIO); + list_for_each(list, &card->devices) { + dev = snd_device(list); + if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) { + if ((err = dev->ops->dev_register(dev)) < 0) + return err; + dev->state = SNDRV_DEV_REGISTERED; + } + } + return 0; +} + +/* + * disconnect all the devices on the card. + * called from init.c + */ +int snd_device_disconnect_all(snd_card_t *card) +{ + snd_device_t *dev; + struct list_head *list; + int err = 0; + + snd_assert(card != NULL, return -ENXIO); + list_for_each(list, &card->devices) { + dev = snd_device(list); + if (snd_device_disconnect(card, dev->device_data) < 0) + err = -ENXIO; + } + return err; +} + +/* + * release all the devices on the card. + * called from init.c + */ +int snd_device_free_all(snd_card_t *card, snd_device_cmd_t cmd) +{ + snd_device_t *dev; + struct list_head *list; + int err; + unsigned int range_low, range_high; + + snd_assert(card != NULL, return -ENXIO); + range_low = cmd * SNDRV_DEV_TYPE_RANGE_SIZE; + range_high = range_low + SNDRV_DEV_TYPE_RANGE_SIZE - 1; + __again: + list_for_each(list, &card->devices) { + dev = snd_device(list); + if (dev->type >= range_low && dev->type <= range_high) { + if ((err = snd_device_free(card, dev->device_data)) < 0) + return err; + goto __again; + } + } + return 0; +} diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c new file mode 100644 index 000000000000..997dd41c584e --- /dev/null +++ b/sound/core/hwdep.c @@ -0,0 +1,524 @@ +/* + * Hardware dependent layer + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/major.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/minors.h> +#include <sound/hwdep.h> +#include <sound/info.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Hardware dependent layer"); +MODULE_LICENSE("GPL"); + +static snd_hwdep_t *snd_hwdep_devices[SNDRV_CARDS * SNDRV_MINOR_HWDEPS]; + +static DECLARE_MUTEX(register_mutex); + +static int snd_hwdep_free(snd_hwdep_t *hwdep); +static int snd_hwdep_dev_free(snd_device_t *device); +static int snd_hwdep_dev_register(snd_device_t *device); +static int snd_hwdep_dev_unregister(snd_device_t *device); + +/* + + */ + +static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig) +{ + snd_hwdep_t *hw = file->private_data; + if (hw->ops.llseek) + return hw->ops.llseek(hw, file, offset, orig); + return -ENXIO; +} + +static ssize_t snd_hwdep_read(struct file * file, char __user *buf, size_t count, loff_t *offset) +{ + snd_hwdep_t *hw = file->private_data; + if (hw->ops.read) + return hw->ops.read(hw, buf, count, offset); + return -ENXIO; +} + +static ssize_t snd_hwdep_write(struct file * file, const char __user *buf, size_t count, loff_t *offset) +{ + snd_hwdep_t *hw = file->private_data; + if (hw->ops.write) + return hw->ops.write(hw, buf, count, offset); + return -ENXIO; +} + +static int snd_hwdep_open(struct inode *inode, struct file * file) +{ + int major = imajor(inode); + int cardnum; + int device; + snd_hwdep_t *hw; + int err; + wait_queue_t wait; + + switch (major) { + case CONFIG_SND_MAJOR: + cardnum = SNDRV_MINOR_CARD(iminor(inode)); + device = SNDRV_MINOR_DEVICE(iminor(inode)) - SNDRV_MINOR_HWDEP; + break; +#ifdef CONFIG_SND_OSSEMUL + case SOUND_MAJOR: + cardnum = SNDRV_MINOR_OSS_CARD(iminor(inode)); + device = 0; + break; +#endif + default: + return -ENXIO; + } + cardnum %= SNDRV_CARDS; + device %= SNDRV_MINOR_HWDEPS; + hw = snd_hwdep_devices[(cardnum * SNDRV_MINOR_HWDEPS) + device]; + if (hw == NULL) + return -ENODEV; + + if (!hw->ops.open) + return -ENXIO; +#ifdef CONFIG_SND_OSSEMUL + if (major == SOUND_MAJOR && hw->oss_type < 0) + return -ENXIO; +#endif + + if (!try_module_get(hw->card->module)) + return -EFAULT; + + init_waitqueue_entry(&wait, current); + add_wait_queue(&hw->open_wait, &wait); + down(&hw->open_mutex); + while (1) { + if (hw->exclusive && hw->used > 0) { + err = -EBUSY; + break; + } + err = hw->ops.open(hw, file); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (file->f_flags & O_NONBLOCK) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + up(&hw->open_mutex); + schedule(); + down(&hw->open_mutex); + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&hw->open_wait, &wait); + if (err >= 0) { + err = snd_card_file_add(hw->card, file); + if (err >= 0) { + file->private_data = hw; + hw->used++; + } else { + if (hw->ops.release) + hw->ops.release(hw, file); + } + } + up(&hw->open_mutex); + if (err < 0) + module_put(hw->card->module); + return err; +} + +static int snd_hwdep_release(struct inode *inode, struct file * file) +{ + int err = -ENXIO; + snd_hwdep_t *hw = file->private_data; + down(&hw->open_mutex); + if (hw->ops.release) { + err = hw->ops.release(hw, file); + wake_up(&hw->open_wait); + } + if (hw->used > 0) + hw->used--; + snd_card_file_remove(hw->card, file); + up(&hw->open_mutex); + module_put(hw->card->module); + return err; +} + +static unsigned int snd_hwdep_poll(struct file * file, poll_table * wait) +{ + snd_hwdep_t *hw = file->private_data; + if (hw->ops.poll) + return hw->ops.poll(hw, file, wait); + return 0; +} + +static int snd_hwdep_info(snd_hwdep_t *hw, snd_hwdep_info_t __user *_info) +{ + snd_hwdep_info_t info; + + memset(&info, 0, sizeof(info)); + info.card = hw->card->number; + strlcpy(info.id, hw->id, sizeof(info.id)); + strlcpy(info.name, hw->name, sizeof(info.name)); + info.iface = hw->iface; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_hwdep_dsp_status(snd_hwdep_t *hw, snd_hwdep_dsp_status_t __user *_info) +{ + snd_hwdep_dsp_status_t info; + int err; + + if (! hw->ops.dsp_status) + return -ENXIO; + memset(&info, 0, sizeof(info)); + info.dsp_loaded = hw->dsp_loaded; + if ((err = hw->ops.dsp_status(hw, &info)) < 0) + return err; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_hwdep_dsp_load(snd_hwdep_t *hw, snd_hwdep_dsp_image_t __user *_info) +{ + snd_hwdep_dsp_image_t info; + int err; + + if (! hw->ops.dsp_load) + return -ENXIO; + memset(&info, 0, sizeof(info)); + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + /* check whether the dsp was already loaded */ + if (hw->dsp_loaded & (1 << info.index)) + return -EBUSY; + if (!access_ok(VERIFY_READ, info.image, info.length)) + return -EFAULT; + err = hw->ops.dsp_load(hw, &info); + if (err < 0) + return err; + hw->dsp_loaded |= (1 << info.index); + return 0; +} + +static long snd_hwdep_ioctl(struct file * file, unsigned int cmd, unsigned long arg) +{ + snd_hwdep_t *hw = file->private_data; + void __user *argp = (void __user *)arg; + switch (cmd) { + case SNDRV_HWDEP_IOCTL_PVERSION: + return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp); + case SNDRV_HWDEP_IOCTL_INFO: + return snd_hwdep_info(hw, argp); + case SNDRV_HWDEP_IOCTL_DSP_STATUS: + return snd_hwdep_dsp_status(hw, argp); + case SNDRV_HWDEP_IOCTL_DSP_LOAD: + return snd_hwdep_dsp_load(hw, argp); + } + if (hw->ops.ioctl) + return hw->ops.ioctl(hw, file, cmd, arg); + return -ENOTTY; +} + +static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma) +{ + snd_hwdep_t *hw = file->private_data; + if (hw->ops.mmap) + return hw->ops.mmap(hw, file, vma); + return -ENXIO; +} + +static int snd_hwdep_control_ioctl(snd_card_t * card, snd_ctl_file_t * control, + unsigned int cmd, unsigned long arg) +{ + unsigned int tmp; + + tmp = card->number * SNDRV_MINOR_HWDEPS; + switch (cmd) { + case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE: + { + int device; + + if (get_user(device, (int __user *)arg)) + return -EFAULT; + device = device < 0 ? 0 : device + 1; + while (device < SNDRV_MINOR_HWDEPS) { + if (snd_hwdep_devices[tmp + device]) + break; + device++; + } + if (device >= SNDRV_MINOR_HWDEPS) + device = -1; + if (put_user(device, (int __user *)arg)) + return -EFAULT; + return 0; + } + case SNDRV_CTL_IOCTL_HWDEP_INFO: + { + snd_hwdep_info_t __user *info = (snd_hwdep_info_t __user *)arg; + int device; + snd_hwdep_t *hwdep; + + if (get_user(device, &info->device)) + return -EFAULT; + if (device < 0 || device >= SNDRV_MINOR_HWDEPS) + return -ENXIO; + hwdep = snd_hwdep_devices[tmp + device]; + if (hwdep == NULL) + return -ENXIO; + return snd_hwdep_info(hwdep, info); + } + } + return -ENOIOCTLCMD; +} + +#ifdef CONFIG_COMPAT +#include "hwdep_compat.c" +#else +#define snd_hwdep_ioctl_compat NULL +#endif + +/* + + */ + +static struct file_operations snd_hwdep_f_ops = +{ + .owner = THIS_MODULE, + .llseek = snd_hwdep_llseek, + .read = snd_hwdep_read, + .write = snd_hwdep_write, + .open = snd_hwdep_open, + .release = snd_hwdep_release, + .poll = snd_hwdep_poll, + .unlocked_ioctl = snd_hwdep_ioctl, + .compat_ioctl = snd_hwdep_ioctl_compat, + .mmap = snd_hwdep_mmap, +}; + +static snd_minor_t snd_hwdep_reg = +{ + .comment = "hardware dependent", + .f_ops = &snd_hwdep_f_ops, +}; + +/** + * snd_hwdep_new - create a new hwdep instance + * @card: the card instance + * @id: the id string + * @device: the device index (zero-based) + * @rhwdep: the pointer to store the new hwdep instance + * + * Creates a new hwdep instance with the given index on the card. + * The callbacks (hwdep->ops) must be set on the returned instance + * after this call manually by the caller. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_hwdep_new(snd_card_t * card, char *id, int device, snd_hwdep_t ** rhwdep) +{ + snd_hwdep_t *hwdep; + int err; + static snd_device_ops_t ops = { + .dev_free = snd_hwdep_dev_free, + .dev_register = snd_hwdep_dev_register, + .dev_unregister = snd_hwdep_dev_unregister + }; + + snd_assert(rhwdep != NULL, return -EINVAL); + *rhwdep = NULL; + snd_assert(card != NULL, return -ENXIO); + hwdep = kcalloc(1, sizeof(*hwdep), GFP_KERNEL); + if (hwdep == NULL) + return -ENOMEM; + hwdep->card = card; + hwdep->device = device; + if (id) { + strlcpy(hwdep->id, id, sizeof(hwdep->id)); + } +#ifdef CONFIG_SND_OSSEMUL + hwdep->oss_type = -1; +#endif + if ((err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops)) < 0) { + snd_hwdep_free(hwdep); + return err; + } + init_waitqueue_head(&hwdep->open_wait); + init_MUTEX(&hwdep->open_mutex); + *rhwdep = hwdep; + return 0; +} + +static int snd_hwdep_free(snd_hwdep_t *hwdep) +{ + snd_assert(hwdep != NULL, return -ENXIO); + if (hwdep->private_free) + hwdep->private_free(hwdep); + kfree(hwdep); + return 0; +} + +static int snd_hwdep_dev_free(snd_device_t *device) +{ + snd_hwdep_t *hwdep = device->device_data; + return snd_hwdep_free(hwdep); +} + +static int snd_hwdep_dev_register(snd_device_t *device) +{ + snd_hwdep_t *hwdep = device->device_data; + int idx, err; + char name[32]; + + down(®ister_mutex); + idx = (hwdep->card->number * SNDRV_MINOR_HWDEPS) + hwdep->device; + if (snd_hwdep_devices[idx]) { + up(®ister_mutex); + return -EBUSY; + } + snd_hwdep_devices[idx] = hwdep; + sprintf(name, "hwC%iD%i", hwdep->card->number, hwdep->device); + if ((err = snd_register_device(SNDRV_DEVICE_TYPE_HWDEP, + hwdep->card, hwdep->device, + &snd_hwdep_reg, name)) < 0) { + snd_printk(KERN_ERR "unable to register hardware dependent device %i:%i\n", + hwdep->card->number, hwdep->device); + snd_hwdep_devices[idx] = NULL; + up(®ister_mutex); + return err; + } +#ifdef CONFIG_SND_OSSEMUL + hwdep->ossreg = 0; + if (hwdep->oss_type >= 0) { + if ((hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM) && (hwdep->device != 0)) { + snd_printk (KERN_WARNING "only hwdep device 0 can be registered as OSS direct FM device!\n"); + } else { + if (snd_register_oss_device(hwdep->oss_type, + hwdep->card, hwdep->device, + &snd_hwdep_reg, hwdep->oss_dev) < 0) { + snd_printk(KERN_ERR "unable to register OSS compatibility device %i:%i\n", + hwdep->card->number, hwdep->device); + } else + hwdep->ossreg = 1; + } + } +#endif + up(®ister_mutex); + return 0; +} + +static int snd_hwdep_dev_unregister(snd_device_t *device) +{ + snd_hwdep_t *hwdep = device->device_data; + int idx; + + snd_assert(hwdep != NULL, return -ENXIO); + down(®ister_mutex); + idx = (hwdep->card->number * SNDRV_MINOR_HWDEPS) + hwdep->device; + if (snd_hwdep_devices[idx] != hwdep) { + up(®ister_mutex); + return -EINVAL; + } +#ifdef CONFIG_SND_OSSEMUL + if (hwdep->ossreg) + snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device); +#endif + snd_unregister_device(SNDRV_DEVICE_TYPE_HWDEP, hwdep->card, hwdep->device); + snd_hwdep_devices[idx] = NULL; + up(®ister_mutex); + return snd_hwdep_free(hwdep); +} + +/* + * Info interface + */ + +static void snd_hwdep_proc_read(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + int idx; + snd_hwdep_t *hwdep; + + down(®ister_mutex); + for (idx = 0; idx < SNDRV_CARDS * SNDRV_MINOR_HWDEPS; idx++) { + hwdep = snd_hwdep_devices[idx]; + if (hwdep == NULL) + continue; + snd_iprintf(buffer, "%02i-%02i: %s\n", + idx / SNDRV_MINOR_HWDEPS, + idx % SNDRV_MINOR_HWDEPS, + hwdep->name); + } + up(®ister_mutex); +} + +/* + * ENTRY functions + */ + +static snd_info_entry_t *snd_hwdep_proc_entry = NULL; + +static int __init alsa_hwdep_init(void) +{ + snd_info_entry_t *entry; + + memset(snd_hwdep_devices, 0, sizeof(snd_hwdep_devices)); + if ((entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL)) != NULL) { + entry->c.text.read_size = 512; + entry->c.text.read = snd_hwdep_proc_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_hwdep_proc_entry = entry; + snd_ctl_register_ioctl(snd_hwdep_control_ioctl); + snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl); + return 0; +} + +static void __exit alsa_hwdep_exit(void) +{ + snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl); + snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl); + if (snd_hwdep_proc_entry) { + snd_info_unregister(snd_hwdep_proc_entry); + snd_hwdep_proc_entry = NULL; + } +} + +module_init(alsa_hwdep_init) +module_exit(alsa_hwdep_exit) + +EXPORT_SYMBOL(snd_hwdep_new); diff --git a/sound/core/hwdep_compat.c b/sound/core/hwdep_compat.c new file mode 100644 index 000000000000..6866f423d4b9 --- /dev/null +++ b/sound/core/hwdep_compat.c @@ -0,0 +1,77 @@ +/* + * 32bit -> 64bit ioctl wrapper for hwdep API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This file is included from hwdep.c */ + +#include <linux/compat.h> + +struct sndrv_hwdep_dsp_image32 { + u32 index; + unsigned char name[64]; + u32 image; /* pointer */ + u32 length; + u32 driver_data; +} /* don't set packed attribute here */; + +static int snd_hwdep_dsp_load_compat(snd_hwdep_t *hw, + struct sndrv_hwdep_dsp_image32 __user *src) +{ + struct sndrv_hwdep_dsp_image *dst; + compat_caddr_t ptr; + u32 val; + + dst = compat_alloc_user_space(sizeof(*dst)); + + /* index and name */ + if (copy_in_user(dst, src, 4 + 64)) + return -EFAULT; + if (get_user(ptr, &src->image) || + put_user(compat_ptr(ptr), &dst->image)) + return -EFAULT; + if (get_user(val, &src->length) || + put_user(val, &dst->length)) + return -EFAULT; + if (get_user(val, &src->driver_data) || + put_user(val, &dst->driver_data)) + return -EFAULT; + + return snd_hwdep_dsp_load(hw, dst); +} + +enum { + SNDRV_HWDEP_IOCTL_DSP_LOAD32 = _IOW('H', 0x03, struct sndrv_hwdep_dsp_image32) +}; + +static long snd_hwdep_ioctl_compat(struct file * file, unsigned int cmd, unsigned long arg) +{ + snd_hwdep_t *hw = file->private_data; + void __user *argp = compat_ptr(arg); + switch (cmd) { + case SNDRV_HWDEP_IOCTL_PVERSION: + case SNDRV_HWDEP_IOCTL_INFO: + case SNDRV_HWDEP_IOCTL_DSP_STATUS: + return snd_hwdep_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_HWDEP_IOCTL_DSP_LOAD32: + return snd_hwdep_dsp_load_compat(hw, argp); + } + if (hw->ops.ioctl_compat) + return hw->ops.ioctl_compat(hw, file, cmd, arg); + return -ENOIOCTLCMD; +} diff --git a/sound/core/info.c b/sound/core/info.c new file mode 100644 index 000000000000..31faffe01cb0 --- /dev/null +++ b/sound/core/info.c @@ -0,0 +1,989 @@ +/* + * Information interface for ALSA driver + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/time.h> +#include <linux/smp_lock.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <sound/version.h> +#include <linux/proc_fs.h> +#include <linux/devfs_fs_kernel.h> +#include <stdarg.h> + +/* + * + */ + +int snd_info_check_reserved_words(const char *str) +{ + static char *reserved[] = + { + "version", + "meminfo", + "memdebug", + "detect", + "devices", + "oss", + "cards", + "timers", + "synth", + "pcm", + "seq", + NULL + }; + char **xstr = reserved; + + while (*xstr) { + if (!strcmp(*xstr, str)) + return 0; + xstr++; + } + if (!strncmp(str, "card", 4)) + return 0; + return 1; +} + +#ifdef CONFIG_PROC_FS + +static DECLARE_MUTEX(info_mutex); + +typedef struct _snd_info_private_data { + snd_info_buffer_t *rbuffer; + snd_info_buffer_t *wbuffer; + snd_info_entry_t *entry; + void *file_private_data; +} snd_info_private_data_t; + +static int snd_info_version_init(void); +static int snd_info_version_done(void); + + +/** + * snd_iprintf - printf on the procfs buffer + * @buffer: the procfs buffer + * @fmt: the printf format + * + * Outputs the string on the procfs buffer just like printf(). + * + * Returns the size of output string. + */ +int snd_iprintf(snd_info_buffer_t * buffer, char *fmt,...) +{ + va_list args; + int len, res; + + if (buffer->stop || buffer->error) + return 0; + len = buffer->len - buffer->size; + va_start(args, fmt); + res = vsnprintf(buffer->curr, len, fmt, args); + va_end(args); + if (res >= len) { + buffer->stop = 1; + return 0; + } + buffer->curr += res; + buffer->size += res; + return res; +} + +/* + + */ + +static struct proc_dir_entry *snd_proc_root = NULL; +snd_info_entry_t *snd_seq_root = NULL; +#ifdef CONFIG_SND_OSSEMUL +snd_info_entry_t *snd_oss_root = NULL; +#endif + +static inline void snd_info_entry_prepare(struct proc_dir_entry *de) +{ + de->owner = THIS_MODULE; +} + +static void snd_remove_proc_entry(struct proc_dir_entry *parent, + struct proc_dir_entry *de) +{ + if (de) + remove_proc_entry(de->name, parent); +} + +static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig) +{ + snd_info_private_data_t *data; + struct snd_info_entry *entry; + loff_t ret; + + data = file->private_data; + entry = data->entry; + lock_kernel(); + switch (entry->content) { + case SNDRV_INFO_CONTENT_TEXT: + switch (orig) { + case 0: /* SEEK_SET */ + file->f_pos = offset; + ret = file->f_pos; + goto out; + case 1: /* SEEK_CUR */ + file->f_pos += offset; + ret = file->f_pos; + goto out; + case 2: /* SEEK_END */ + default: + ret = -EINVAL; + goto out; + } + break; + case SNDRV_INFO_CONTENT_DATA: + if (entry->c.ops->llseek) { + ret = entry->c.ops->llseek(entry, + data->file_private_data, + file, offset, orig); + goto out; + } + break; + } + ret = -ENXIO; +out: + unlock_kernel(); + return ret; +} + +static ssize_t snd_info_entry_read(struct file *file, char __user *buffer, + size_t count, loff_t * offset) +{ + snd_info_private_data_t *data; + struct snd_info_entry *entry; + snd_info_buffer_t *buf; + size_t size = 0; + loff_t pos; + + data = file->private_data; + snd_assert(data != NULL, return -ENXIO); + pos = *offset; + if (pos < 0 || (long) pos != pos || (ssize_t) count < 0) + return -EIO; + if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos) + return -EIO; + entry = data->entry; + switch (entry->content) { + case SNDRV_INFO_CONTENT_TEXT: + buf = data->rbuffer; + if (buf == NULL) + return -EIO; + if (pos >= buf->size) + return 0; + size = buf->size - pos; + size = min(count, size); + if (copy_to_user(buffer, buf->buffer + pos, size)) + return -EFAULT; + break; + case SNDRV_INFO_CONTENT_DATA: + if (entry->c.ops->read) + size = entry->c.ops->read(entry, + data->file_private_data, + file, buffer, count, pos); + break; + } + if ((ssize_t) size > 0) + *offset = pos + size; + return size; +} + +static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer, + size_t count, loff_t * offset) +{ + snd_info_private_data_t *data; + struct snd_info_entry *entry; + snd_info_buffer_t *buf; + size_t size = 0; + loff_t pos; + + data = file->private_data; + snd_assert(data != NULL, return -ENXIO); + entry = data->entry; + pos = *offset; + if (pos < 0 || (long) pos != pos || (ssize_t) count < 0) + return -EIO; + if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos) + return -EIO; + switch (entry->content) { + case SNDRV_INFO_CONTENT_TEXT: + buf = data->wbuffer; + if (buf == NULL) + return -EIO; + if (pos >= buf->len) + return -ENOMEM; + size = buf->len - pos; + size = min(count, size); + if (copy_from_user(buf->buffer + pos, buffer, size)) + return -EFAULT; + if ((long)buf->size < pos + size) + buf->size = pos + size; + break; + case SNDRV_INFO_CONTENT_DATA: + if (entry->c.ops->write) + size = entry->c.ops->write(entry, + data->file_private_data, + file, buffer, count, pos); + break; + } + if ((ssize_t) size > 0) + *offset = pos + size; + return size; +} + +static int snd_info_entry_open(struct inode *inode, struct file *file) +{ + snd_info_entry_t *entry; + snd_info_private_data_t *data; + snd_info_buffer_t *buffer; + struct proc_dir_entry *p; + int mode, err; + + down(&info_mutex); + p = PDE(inode); + entry = p == NULL ? NULL : (snd_info_entry_t *)p->data; + if (entry == NULL || entry->disconnected) { + up(&info_mutex); + return -ENODEV; + } + if (!try_module_get(entry->module)) { + err = -EFAULT; + goto __error1; + } + mode = file->f_flags & O_ACCMODE; + if (mode == O_RDONLY || mode == O_RDWR) { + if ((entry->content == SNDRV_INFO_CONTENT_TEXT && + !entry->c.text.read_size) || + (entry->content == SNDRV_INFO_CONTENT_DATA && + entry->c.ops->read == NULL)) { + err = -ENODEV; + goto __error; + } + } + if (mode == O_WRONLY || mode == O_RDWR) { + if ((entry->content == SNDRV_INFO_CONTENT_TEXT && + !entry->c.text.write_size) || + (entry->content == SNDRV_INFO_CONTENT_DATA && + entry->c.ops->write == NULL)) { + err = -ENODEV; + goto __error; + } + } + data = kcalloc(1, sizeof(*data), GFP_KERNEL); + if (data == NULL) { + err = -ENOMEM; + goto __error; + } + data->entry = entry; + switch (entry->content) { + case SNDRV_INFO_CONTENT_TEXT: + if (mode == O_RDONLY || mode == O_RDWR) { + buffer = kcalloc(1, sizeof(*buffer), GFP_KERNEL); + if (buffer == NULL) { + kfree(data); + err = -ENOMEM; + goto __error; + } + buffer->len = (entry->c.text.read_size + + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); + buffer->buffer = vmalloc(buffer->len); + if (buffer->buffer == NULL) { + kfree(buffer); + kfree(data); + err = -ENOMEM; + goto __error; + } + buffer->curr = buffer->buffer; + data->rbuffer = buffer; + } + if (mode == O_WRONLY || mode == O_RDWR) { + buffer = kcalloc(1, sizeof(*buffer), GFP_KERNEL); + if (buffer == NULL) { + if (mode == O_RDWR) { + vfree(data->rbuffer->buffer); + kfree(data->rbuffer); + } + kfree(data); + err = -ENOMEM; + goto __error; + } + buffer->len = (entry->c.text.write_size + + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); + buffer->buffer = vmalloc(buffer->len); + if (buffer->buffer == NULL) { + if (mode == O_RDWR) { + vfree(data->rbuffer->buffer); + kfree(data->rbuffer); + } + kfree(buffer); + kfree(data); + err = -ENOMEM; + goto __error; + } + buffer->curr = buffer->buffer; + data->wbuffer = buffer; + } + break; + case SNDRV_INFO_CONTENT_DATA: /* data */ + if (entry->c.ops->open) { + if ((err = entry->c.ops->open(entry, mode, + &data->file_private_data)) < 0) { + kfree(data); + goto __error; + } + } + break; + } + file->private_data = data; + up(&info_mutex); + if (entry->content == SNDRV_INFO_CONTENT_TEXT && + (mode == O_RDONLY || mode == O_RDWR)) { + if (entry->c.text.read) { + down(&entry->access); + entry->c.text.read(entry, data->rbuffer); + up(&entry->access); + } + } + return 0; + + __error: + module_put(entry->module); + __error1: + up(&info_mutex); + return err; +} + +static int snd_info_entry_release(struct inode *inode, struct file *file) +{ + snd_info_entry_t *entry; + snd_info_private_data_t *data; + int mode; + + mode = file->f_flags & O_ACCMODE; + data = file->private_data; + entry = data->entry; + switch (entry->content) { + case SNDRV_INFO_CONTENT_TEXT: + if (mode == O_RDONLY || mode == O_RDWR) { + vfree(data->rbuffer->buffer); + kfree(data->rbuffer); + } + if (mode == O_WRONLY || mode == O_RDWR) { + if (entry->c.text.write) { + entry->c.text.write(entry, data->wbuffer); + if (data->wbuffer->error) { + snd_printk(KERN_WARNING "data write error to %s (%i)\n", + entry->name, + data->wbuffer->error); + } + } + vfree(data->wbuffer->buffer); + kfree(data->wbuffer); + } + break; + case SNDRV_INFO_CONTENT_DATA: + if (entry->c.ops->release) + entry->c.ops->release(entry, mode, + data->file_private_data); + break; + } + module_put(entry->module); + kfree(data); + return 0; +} + +static unsigned int snd_info_entry_poll(struct file *file, poll_table * wait) +{ + snd_info_private_data_t *data; + struct snd_info_entry *entry; + unsigned int mask; + + data = file->private_data; + if (data == NULL) + return 0; + entry = data->entry; + mask = 0; + switch (entry->content) { + case SNDRV_INFO_CONTENT_DATA: + if (entry->c.ops->poll) + return entry->c.ops->poll(entry, + data->file_private_data, + file, wait); + if (entry->c.ops->read) + mask |= POLLIN | POLLRDNORM; + if (entry->c.ops->write) + mask |= POLLOUT | POLLWRNORM; + break; + } + return mask; +} + +static inline int _snd_info_entry_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + snd_info_private_data_t *data; + struct snd_info_entry *entry; + + data = file->private_data; + if (data == NULL) + return 0; + entry = data->entry; + switch (entry->content) { + case SNDRV_INFO_CONTENT_DATA: + if (entry->c.ops->ioctl) + return entry->c.ops->ioctl(entry, + data->file_private_data, + file, cmd, arg); + break; + } + return -ENOTTY; +} + +/* FIXME: need to unlock BKL to allow preemption */ +static int snd_info_entry_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int err; + unlock_kernel(); + err = _snd_info_entry_ioctl(inode, file, cmd, arg); + lock_kernel(); + return err; +} + +static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct inode *inode = file->f_dentry->d_inode; + snd_info_private_data_t *data; + struct snd_info_entry *entry; + + data = file->private_data; + if (data == NULL) + return 0; + entry = data->entry; + switch (entry->content) { + case SNDRV_INFO_CONTENT_DATA: + if (entry->c.ops->mmap) + return entry->c.ops->mmap(entry, + data->file_private_data, + inode, file, vma); + break; + } + return -ENXIO; +} + +static struct file_operations snd_info_entry_operations = +{ + .owner = THIS_MODULE, + .llseek = snd_info_entry_llseek, + .read = snd_info_entry_read, + .write = snd_info_entry_write, + .poll = snd_info_entry_poll, + .ioctl = snd_info_entry_ioctl, + .mmap = snd_info_entry_mmap, + .open = snd_info_entry_open, + .release = snd_info_entry_release, +}; + +/** + * snd_create_proc_entry - create a procfs entry + * @name: the name of the proc file + * @mode: the file permission bits, S_Ixxx + * @parent: the parent proc-directory entry + * + * Creates a new proc file entry with the given name and permission + * on the given directory. + * + * Returns the pointer of new instance or NULL on failure. + */ +static struct proc_dir_entry *snd_create_proc_entry(const char *name, mode_t mode, + struct proc_dir_entry *parent) +{ + struct proc_dir_entry *p; + p = create_proc_entry(name, mode, parent); + if (p) + snd_info_entry_prepare(p); + return p; +} + +int __init snd_info_init(void) +{ + struct proc_dir_entry *p; + + p = snd_create_proc_entry("asound", S_IFDIR | S_IRUGO | S_IXUGO, &proc_root); + if (p == NULL) + return -ENOMEM; + snd_proc_root = p; +#ifdef CONFIG_SND_OSSEMUL + { + snd_info_entry_t *entry; + if ((entry = snd_info_create_module_entry(THIS_MODULE, "oss", NULL)) == NULL) + return -ENOMEM; + entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + snd_oss_root = entry; + } +#endif +#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE) + { + snd_info_entry_t *entry; + if ((entry = snd_info_create_module_entry(THIS_MODULE, "seq", NULL)) == NULL) + return -ENOMEM; + entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + snd_seq_root = entry; + } +#endif + snd_info_version_init(); + snd_memory_info_init(); + snd_minor_info_init(); + snd_minor_info_oss_init(); + snd_card_info_init(); + return 0; +} + +int __exit snd_info_done(void) +{ + snd_card_info_done(); + snd_minor_info_oss_done(); + snd_minor_info_done(); + snd_memory_info_done(); + snd_info_version_done(); + if (snd_proc_root) { +#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE) + if (snd_seq_root) + snd_info_unregister(snd_seq_root); +#endif +#ifdef CONFIG_SND_OSSEMUL + if (snd_oss_root) + snd_info_unregister(snd_oss_root); +#endif + snd_remove_proc_entry(&proc_root, snd_proc_root); + } + return 0; +} + +/* + + */ + + +/* + * create a card proc file + * called from init.c + */ +int snd_info_card_create(snd_card_t * card) +{ + char str[8]; + snd_info_entry_t *entry; + + snd_assert(card != NULL, return -ENXIO); + + sprintf(str, "card%i", card->number); + if ((entry = snd_info_create_module_entry(card->module, str, NULL)) == NULL) + return -ENOMEM; + entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + card->proc_root = entry; + return 0; +} + +/* + * register the card proc file + * called from init.c + */ +int snd_info_card_register(snd_card_t * card) +{ + struct proc_dir_entry *p; + + snd_assert(card != NULL, return -ENXIO); + + if (!strcmp(card->id, card->proc_root->name)) + return 0; + + p = proc_symlink(card->id, snd_proc_root, card->proc_root->name); + if (p == NULL) + return -ENOMEM; + card->proc_root_link = p; + return 0; +} + +/* + * de-register the card proc file + * called from init.c + */ +int snd_info_card_free(snd_card_t * card) +{ + snd_assert(card != NULL, return -ENXIO); + if (card->proc_root_link) { + snd_remove_proc_entry(snd_proc_root, card->proc_root_link); + card->proc_root_link = NULL; + } + if (card->proc_root) { + snd_info_unregister(card->proc_root); + card->proc_root = NULL; + } + return 0; +} + + +/** + * snd_info_get_line - read one line from the procfs buffer + * @buffer: the procfs buffer + * @line: the buffer to store + * @len: the max. buffer size - 1 + * + * Reads one line from the buffer and stores the string. + * + * Returns zero if successful, or 1 if error or EOF. + */ +int snd_info_get_line(snd_info_buffer_t * buffer, char *line, int len) +{ + int c = -1; + + if (len <= 0 || buffer->stop || buffer->error) + return 1; + while (--len > 0) { + c = *buffer->curr++; + if (c == '\n') { + if ((buffer->curr - buffer->buffer) >= (long)buffer->size) { + buffer->stop = 1; + } + break; + } + *line++ = c; + if ((buffer->curr - buffer->buffer) >= (long)buffer->size) { + buffer->stop = 1; + break; + } + } + while (c != '\n' && !buffer->stop) { + c = *buffer->curr++; + if ((buffer->curr - buffer->buffer) >= (long)buffer->size) { + buffer->stop = 1; + } + } + *line = '\0'; + return 0; +} + +/** + * snd_info_get_line - parse a string token + * @dest: the buffer to store the string token + * @src: the original string + * @len: the max. length of token - 1 + * + * Parses the original string and copy a token to the given + * string buffer. + * + * Returns the updated pointer of the original string so that + * it can be used for the next call. + */ +char *snd_info_get_str(char *dest, char *src, int len) +{ + int c; + + while (*src == ' ' || *src == '\t') + src++; + if (*src == '"' || *src == '\'') { + c = *src++; + while (--len > 0 && *src && *src != c) { + *dest++ = *src++; + } + if (*src == c) + src++; + } else { + while (--len > 0 && *src && *src != ' ' && *src != '\t') { + *dest++ = *src++; + } + } + *dest = 0; + while (*src == ' ' || *src == '\t') + src++; + return src; +} + +/** + * snd_info_create_entry - create an info entry + * @name: the proc file name + * + * Creates an info entry with the given file name and initializes as + * the default state. + * + * Usually called from other functions such as + * snd_info_create_card_entry(). + * + * Returns the pointer of the new instance, or NULL on failure. + */ +static snd_info_entry_t *snd_info_create_entry(const char *name) +{ + snd_info_entry_t *entry; + entry = kcalloc(1, sizeof(*entry), GFP_KERNEL); + if (entry == NULL) + return NULL; + entry->name = snd_kmalloc_strdup(name, GFP_KERNEL); + if (entry->name == NULL) { + kfree(entry); + return NULL; + } + entry->mode = S_IFREG | S_IRUGO; + entry->content = SNDRV_INFO_CONTENT_TEXT; + init_MUTEX(&entry->access); + return entry; +} + +/** + * snd_info_create_module_entry - create an info entry for the given module + * @module: the module pointer + * @name: the file name + * @parent: the parent directory + * + * Creates a new info entry and assigns it to the given module. + * + * Returns the pointer of the new instance, or NULL on failure. + */ +snd_info_entry_t *snd_info_create_module_entry(struct module * module, + const char *name, + snd_info_entry_t *parent) +{ + snd_info_entry_t *entry = snd_info_create_entry(name); + if (entry) { + entry->module = module; + entry->parent = parent; + } + return entry; +} + +/** + * snd_info_create_card_entry - create an info entry for the given card + * @card: the card instance + * @name: the file name + * @parent: the parent directory + * + * Creates a new info entry and assigns it to the given card. + * + * Returns the pointer of the new instance, or NULL on failure. + */ +snd_info_entry_t *snd_info_create_card_entry(snd_card_t * card, + const char *name, + snd_info_entry_t * parent) +{ + snd_info_entry_t *entry = snd_info_create_entry(name); + if (entry) { + entry->module = card->module; + entry->card = card; + entry->parent = parent; + } + return entry; +} + +static int snd_info_dev_free_entry(snd_device_t *device) +{ + snd_info_entry_t *entry = device->device_data; + snd_info_free_entry(entry); + return 0; +} + +static int snd_info_dev_register_entry(snd_device_t *device) +{ + snd_info_entry_t *entry = device->device_data; + return snd_info_register(entry); +} + +static int snd_info_dev_disconnect_entry(snd_device_t *device) +{ + snd_info_entry_t *entry = device->device_data; + entry->disconnected = 1; + return 0; +} + +static int snd_info_dev_unregister_entry(snd_device_t *device) +{ + snd_info_entry_t *entry = device->device_data; + return snd_info_unregister(entry); +} + +/** + * snd_card_proc_new - create an info entry for the given card + * @card: the card instance + * @name: the file name + * @entryp: the pointer to store the new info entry + * + * Creates a new info entry and assigns it to the given card. + * Unlike snd_info_create_card_entry(), this function registers the + * info entry as an ALSA device component, so that it can be + * unregistered/released without explicit call. + * Also, you don't have to register this entry via snd_info_register(), + * since this will be registered by snd_card_register() automatically. + * + * The parent is assumed as card->proc_root. + * + * For releasing this entry, use snd_device_free() instead of + * snd_info_free_entry(). + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_card_proc_new(snd_card_t *card, const char *name, + snd_info_entry_t **entryp) +{ + static snd_device_ops_t ops = { + .dev_free = snd_info_dev_free_entry, + .dev_register = snd_info_dev_register_entry, + .dev_disconnect = snd_info_dev_disconnect_entry, + .dev_unregister = snd_info_dev_unregister_entry + }; + snd_info_entry_t *entry; + int err; + + entry = snd_info_create_card_entry(card, name, card->proc_root); + if (! entry) + return -ENOMEM; + if ((err = snd_device_new(card, SNDRV_DEV_INFO, entry, &ops)) < 0) { + snd_info_free_entry(entry); + return err; + } + if (entryp) + *entryp = entry; + return 0; +} + +/** + * snd_info_free_entry - release the info entry + * @entry: the info entry + * + * Releases the info entry. Don't call this after registered. + */ +void snd_info_free_entry(snd_info_entry_t * entry) +{ + if (entry == NULL) + return; + kfree(entry->name); + if (entry->private_free) + entry->private_free(entry); + kfree(entry); +} + +/** + * snd_info_register - register the info entry + * @entry: the info entry + * + * Registers the proc info entry. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_info_register(snd_info_entry_t * entry) +{ + struct proc_dir_entry *root, *p = NULL; + + snd_assert(entry != NULL, return -ENXIO); + root = entry->parent == NULL ? snd_proc_root : entry->parent->p; + down(&info_mutex); + p = snd_create_proc_entry(entry->name, entry->mode, root); + if (!p) { + up(&info_mutex); + return -ENOMEM; + } + p->owner = entry->module; + if (!S_ISDIR(entry->mode)) + p->proc_fops = &snd_info_entry_operations; + p->size = entry->size; + p->data = entry; + entry->p = p; + up(&info_mutex); + return 0; +} + +/** + * snd_info_unregister - de-register the info entry + * @entry: the info entry + * + * De-registers the info entry and releases the instance. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_info_unregister(snd_info_entry_t * entry) +{ + struct proc_dir_entry *root; + + snd_assert(entry != NULL && entry->p != NULL, return -ENXIO); + root = entry->parent == NULL ? snd_proc_root : entry->parent->p; + snd_assert(root, return -ENXIO); + down(&info_mutex); + snd_remove_proc_entry(root, entry->p); + up(&info_mutex); + snd_info_free_entry(entry); + return 0; +} + +/* + + */ + +static snd_info_entry_t *snd_info_version_entry = NULL; + +static void snd_info_version_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + snd_iprintf(buffer, + "Advanced Linux Sound Architecture Driver Version " + CONFIG_SND_VERSION CONFIG_SND_DATE ".\n" + ); +} + +static int __init snd_info_version_init(void) +{ + snd_info_entry_t *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL); + if (entry == NULL) + return -ENOMEM; + entry->c.text.read_size = 256; + entry->c.text.read = snd_info_version_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + snd_info_version_entry = entry; + return 0; +} + +static int __exit snd_info_version_done(void) +{ + if (snd_info_version_entry) + snd_info_unregister(snd_info_version_entry); + return 0; +} + +#endif /* CONFIG_PROC_FS */ diff --git a/sound/core/info_oss.c b/sound/core/info_oss.c new file mode 100644 index 000000000000..f9e4ce443454 --- /dev/null +++ b/sound/core/info_oss.c @@ -0,0 +1,137 @@ +/* + * Information interface for ALSA driver + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <sound/version.h> +#include <linux/utsname.h> + +#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS) + +/* + * OSS compatible part + */ + +static DECLARE_MUTEX(strings); +static char *snd_sndstat_strings[SNDRV_CARDS][SNDRV_OSS_INFO_DEV_COUNT]; +static snd_info_entry_t *snd_sndstat_proc_entry; + +int snd_oss_info_register(int dev, int num, char *string) +{ + char *x; + + snd_assert(dev >= 0 && dev < SNDRV_OSS_INFO_DEV_COUNT, return -ENXIO); + snd_assert(num >= 0 && num < SNDRV_CARDS, return -ENXIO); + down(&strings); + if (string == NULL) { + if ((x = snd_sndstat_strings[num][dev]) != NULL) { + kfree(x); + x = NULL; + } + } else { + x = snd_kmalloc_strdup(string, GFP_KERNEL); + if (x == NULL) { + up(&strings); + return -ENOMEM; + } + } + snd_sndstat_strings[num][dev] = x; + up(&strings); + return 0; +} + +extern void snd_card_info_read_oss(snd_info_buffer_t * buffer); + +static int snd_sndstat_show_strings(snd_info_buffer_t * buf, char *id, int dev) +{ + int idx, ok = -1; + char *str; + + snd_iprintf(buf, "\n%s:", id); + down(&strings); + for (idx = 0; idx < SNDRV_CARDS; idx++) { + str = snd_sndstat_strings[idx][dev]; + if (str) { + if (ok < 0) { + snd_iprintf(buf, "\n"); + ok++; + } + snd_iprintf(buf, "%i: %s\n", idx, str); + } + } + up(&strings); + if (ok < 0) + snd_iprintf(buf, " NOT ENABLED IN CONFIG\n"); + return ok; +} + +static void snd_sndstat_proc_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + snd_iprintf(buffer, "Sound Driver:3.8.1a-980706 (ALSA v" CONFIG_SND_VERSION " emulation code)\n"); + snd_iprintf(buffer, "Kernel: %s %s %s %s %s\n", + system_utsname.sysname, + system_utsname.nodename, + system_utsname.release, + system_utsname.version, + system_utsname.machine); + snd_iprintf(buffer, "Config options: 0\n"); + snd_iprintf(buffer, "\nInstalled drivers: \n"); + snd_iprintf(buffer, "Type 10: ALSA emulation\n"); + snd_iprintf(buffer, "\nCard config: \n"); + snd_card_info_read_oss(buffer); + snd_sndstat_show_strings(buffer, "Audio devices", SNDRV_OSS_INFO_DEV_AUDIO); + snd_sndstat_show_strings(buffer, "Synth devices", SNDRV_OSS_INFO_DEV_SYNTH); + snd_sndstat_show_strings(buffer, "Midi devices", SNDRV_OSS_INFO_DEV_MIDI); + snd_sndstat_show_strings(buffer, "Timers", SNDRV_OSS_INFO_DEV_TIMERS); + snd_sndstat_show_strings(buffer, "Mixers", SNDRV_OSS_INFO_DEV_MIXERS); +} + +int snd_info_minor_register(void) +{ + snd_info_entry_t *entry; + + memset(snd_sndstat_strings, 0, sizeof(snd_sndstat_strings)); + if ((entry = snd_info_create_module_entry(THIS_MODULE, "sndstat", snd_oss_root)) != NULL) { + entry->c.text.read_size = 2048; + entry->c.text.read = snd_sndstat_proc_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_sndstat_proc_entry = entry; + return 0; +} + +int snd_info_minor_unregister(void) +{ + if (snd_sndstat_proc_entry) { + snd_info_unregister(snd_sndstat_proc_entry); + snd_sndstat_proc_entry = NULL; + } + return 0; +} + +#endif /* CONFIG_SND_OSSEMUL */ diff --git a/sound/core/init.c b/sound/core/init.c new file mode 100644 index 000000000000..3f1fa8eabb72 --- /dev/null +++ b/sound/core/init.c @@ -0,0 +1,887 @@ +/* + * Initialization routines + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/file.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/ctype.h> +#include <linux/pci.h> +#include <linux/pm.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/info.h> + +struct snd_shutdown_f_ops { + struct file_operations f_ops; + struct snd_shutdown_f_ops *next; +}; + +unsigned int snd_cards_lock = 0; /* locked for registering/using */ +snd_card_t *snd_cards[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = NULL}; +DEFINE_RWLOCK(snd_card_rwlock); + +#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) +int (*snd_mixer_oss_notify_callback)(snd_card_t *card, int free_flag); +#endif + +static void snd_card_id_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + snd_iprintf(buffer, "%s\n", entry->card->id); +} + +static void snd_card_free_thread(void * __card); + +/** + * snd_card_new - create and initialize a soundcard structure + * @idx: card index (address) [0 ... (SNDRV_CARDS-1)] + * @xid: card identification (ASCII string) + * @module: top level module for locking + * @extra_size: allocate this extra size after the main soundcard structure + * + * Creates and initializes a soundcard structure. + * + * Returns kmallocated snd_card_t structure. Creates the ALSA control interface + * (which is blocked until snd_card_register function is called). + */ +snd_card_t *snd_card_new(int idx, const char *xid, + struct module *module, int extra_size) +{ + snd_card_t *card; + int err; + + if (extra_size < 0) + extra_size = 0; + card = kcalloc(1, sizeof(*card) + extra_size, GFP_KERNEL); + if (card == NULL) + return NULL; + if (xid) { + if (!snd_info_check_reserved_words(xid)) + goto __error; + strlcpy(card->id, xid, sizeof(card->id)); + } + err = 0; + write_lock(&snd_card_rwlock); + if (idx < 0) { + int idx2; + for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++) + if (~snd_cards_lock & idx & 1<<idx2) { + idx = idx2; + if (idx >= snd_ecards_limit) + snd_ecards_limit = idx + 1; + break; + } + } else if (idx < snd_ecards_limit) { + if (snd_cards_lock & (1 << idx)) + err = -ENODEV; /* invalid */ + } else if (idx < SNDRV_CARDS) + snd_ecards_limit = idx + 1; /* increase the limit */ + else + err = -ENODEV; + if (idx < 0 || err < 0) { + write_unlock(&snd_card_rwlock); + snd_printk(KERN_ERR "cannot find the slot for index %d (range 0-%i)\n", idx, snd_ecards_limit - 1); + goto __error; + } + snd_cards_lock |= 1 << idx; /* lock it */ + write_unlock(&snd_card_rwlock); + card->number = idx; + card->module = module; + INIT_LIST_HEAD(&card->devices); + init_rwsem(&card->controls_rwsem); + rwlock_init(&card->ctl_files_rwlock); + INIT_LIST_HEAD(&card->controls); + INIT_LIST_HEAD(&card->ctl_files); + spin_lock_init(&card->files_lock); + init_waitqueue_head(&card->shutdown_sleep); + INIT_WORK(&card->free_workq, snd_card_free_thread, card); +#ifdef CONFIG_PM + init_MUTEX(&card->power_lock); + init_waitqueue_head(&card->power_sleep); +#endif + /* the control interface cannot be accessed from the user space until */ + /* snd_cards_bitmask and snd_cards are set with snd_card_register */ + if ((err = snd_ctl_create(card)) < 0) { + snd_printd("unable to register control minors\n"); + goto __error; + } + if ((err = snd_info_card_create(card)) < 0) { + snd_printd("unable to create card info\n"); + goto __error_ctl; + } + if (extra_size > 0) + card->private_data = (char *)card + sizeof(snd_card_t); + return card; + + __error_ctl: + snd_device_free_all(card, SNDRV_DEV_CMD_PRE); + __error: + kfree(card); + return NULL; +} + +static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait) +{ + return POLLERR | POLLNVAL; +} + +/** + * snd_card_disconnect - disconnect all APIs from the file-operations (user space) + * @card: soundcard structure + * + * Disconnects all APIs from the file-operations (user space). + * + * Returns zero, otherwise a negative error code. + * + * Note: The current implementation replaces all active file->f_op with special + * dummy file operations (they do nothing except release). + */ +int snd_card_disconnect(snd_card_t * card) +{ + struct snd_monitor_file *mfile; + struct file *file; + struct snd_shutdown_f_ops *s_f_ops; + struct file_operations *f_ops, *old_f_ops; + int err; + + spin_lock(&card->files_lock); + if (card->shutdown) { + spin_unlock(&card->files_lock); + return 0; + } + card->shutdown = 1; + spin_unlock(&card->files_lock); + + /* phase 1: disable fops (user space) operations for ALSA API */ + write_lock(&snd_card_rwlock); + snd_cards[card->number] = NULL; + write_unlock(&snd_card_rwlock); + + /* phase 2: replace file->f_op with special dummy operations */ + + spin_lock(&card->files_lock); + mfile = card->files; + while (mfile) { + file = mfile->file; + + /* it's critical part, use endless loop */ + /* we have no room to fail */ + s_f_ops = kmalloc(sizeof(struct snd_shutdown_f_ops), GFP_ATOMIC); + if (s_f_ops == NULL) + panic("Atomic allocation failed for snd_shutdown_f_ops!"); + + f_ops = &s_f_ops->f_ops; + + memset(f_ops, 0, sizeof(*f_ops)); + f_ops->owner = file->f_op->owner; + f_ops->release = file->f_op->release; + f_ops->poll = snd_disconnect_poll; + + s_f_ops->next = card->s_f_ops; + card->s_f_ops = s_f_ops; + + f_ops = fops_get(f_ops); + + old_f_ops = file->f_op; + file->f_op = f_ops; /* must be atomic */ + fops_put(old_f_ops); + + mfile = mfile->next; + } + spin_unlock(&card->files_lock); + + /* phase 3: notify all connected devices about disconnection */ + /* at this point, they cannot respond to any calls except release() */ + +#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT); +#endif + + /* notify all devices that we are disconnected */ + err = snd_device_disconnect_all(card); + if (err < 0) + snd_printk(KERN_ERR "not all devices for card %i can be disconnected\n", card->number); + + return 0; +} + +#if defined(CONFIG_PM) && defined(CONFIG_SND_GENERIC_PM) +static void snd_generic_device_unregister(struct snd_generic_device *dev); +#endif + +/** + * snd_card_free - frees given soundcard structure + * @card: soundcard structure + * + * This function releases the soundcard structure and the all assigned + * devices automatically. That is, you don't have to release the devices + * by yourself. + * + * Returns zero. Frees all associated devices and frees the control + * interface associated to given soundcard. + */ +int snd_card_free(snd_card_t * card) +{ + struct snd_shutdown_f_ops *s_f_ops; + + if (card == NULL) + return -EINVAL; + write_lock(&snd_card_rwlock); + snd_cards[card->number] = NULL; + write_unlock(&snd_card_rwlock); + +#ifdef CONFIG_PM + wake_up(&card->power_sleep); +#ifdef CONFIG_SND_GENERIC_PM + if (card->pm_dev) { + snd_generic_device_unregister(card->pm_dev); + card->pm_dev = NULL; + } +#endif +#endif + + /* wait, until all devices are ready for the free operation */ + wait_event(card->shutdown_sleep, card->files == NULL); + +#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); +#endif + if (snd_device_free_all(card, SNDRV_DEV_CMD_PRE) < 0) { + snd_printk(KERN_ERR "unable to free all devices (pre)\n"); + /* Fatal, but this situation should never occur */ + } + if (snd_device_free_all(card, SNDRV_DEV_CMD_NORMAL) < 0) { + snd_printk(KERN_ERR "unable to free all devices (normal)\n"); + /* Fatal, but this situation should never occur */ + } + if (snd_device_free_all(card, SNDRV_DEV_CMD_POST) < 0) { + snd_printk(KERN_ERR "unable to free all devices (post)\n"); + /* Fatal, but this situation should never occur */ + } + if (card->private_free) + card->private_free(card); + if (card->proc_id) + snd_info_unregister(card->proc_id); + if (snd_info_card_free(card) < 0) { + snd_printk(KERN_WARNING "unable to free card info\n"); + /* Not fatal error */ + } + while (card->s_f_ops) { + s_f_ops = card->s_f_ops; + card->s_f_ops = s_f_ops->next; + kfree(s_f_ops); + } + write_lock(&snd_card_rwlock); + snd_cards_lock &= ~(1 << card->number); + write_unlock(&snd_card_rwlock); + kfree(card); + return 0; +} + +static void snd_card_free_thread(void * __card) +{ + snd_card_t *card = __card; + struct module * module = card->module; + + if (!try_module_get(module)) { + snd_printk(KERN_ERR "unable to lock toplevel module for card %i in free thread\n", card->number); + module = NULL; + } + + snd_card_free(card); + + module_put(module); +} + +/** + * snd_card_free_in_thread - call snd_card_free() in thread + * @card: soundcard structure + * + * This function schedules the call of snd_card_free() function in a + * work queue. When all devices are released (non-busy), the work + * is woken up and calls snd_card_free(). + * + * When a card can be disconnected at any time by hotplug service, + * this function should be used in disconnect (or detach) callback + * instead of calling snd_card_free() directly. + * + * Returns - zero otherwise a negative error code if the start of thread failed. + */ +int snd_card_free_in_thread(snd_card_t * card) +{ + if (card->files == NULL) { + snd_card_free(card); + return 0; + } + + if (schedule_work(&card->free_workq)) + return 0; + + snd_printk(KERN_ERR "schedule_work() failed in snd_card_free_in_thread for card %i\n", card->number); + /* try to free the structure immediately */ + snd_card_free(card); + return -EFAULT; +} + +static void choose_default_id(snd_card_t * card) +{ + int i, len, idx_flag = 0, loops = 8; + char *id, *spos; + + id = spos = card->shortname; + while (*id != '\0') { + if (*id == ' ') + spos = id + 1; + id++; + } + id = card->id; + while (*spos != '\0' && !isalnum(*spos)) + spos++; + if (isdigit(*spos)) + *id++ = isalpha(card->shortname[0]) ? card->shortname[0] : 'D'; + while (*spos != '\0' && (size_t)(id - card->id) < sizeof(card->id) - 1) { + if (isalnum(*spos)) + *id++ = *spos; + spos++; + } + *id = '\0'; + + id = card->id; + + if (*id == '\0') + strcpy(id, "default"); + + while (1) { + if (loops-- == 0) { + snd_printk(KERN_ERR "unable to choose default card id (%s)\n", id); + strcpy(card->id, card->proc_root->name); + return; + } + if (!snd_info_check_reserved_words(id)) + goto __change; + for (i = 0; i < snd_ecards_limit; i++) { + if (snd_cards[i] && !strcmp(snd_cards[i]->id, id)) + goto __change; + } + break; + + __change: + len = strlen(id); + if (idx_flag) + id[len-1]++; + else if ((size_t)len <= sizeof(card->id) - 3) { + strcat(id, "_1"); + idx_flag++; + } else { + spos = id + len - 2; + if ((size_t)len <= sizeof(card->id) - 2) + spos++; + *spos++ = '_'; + *spos++ = '1'; + *spos++ = '\0'; + idx_flag++; + } + } +} + +/** + * snd_card_register - register the soundcard + * @card: soundcard structure + * + * This function registers all the devices assigned to the soundcard. + * Until calling this, the ALSA control interface is blocked from the + * external accesses. Thus, you should call this function at the end + * of the initialization of the card. + * + * Returns zero otherwise a negative error code if the registrain failed. + */ +int snd_card_register(snd_card_t * card) +{ + int err; + snd_info_entry_t *entry; + + snd_runtime_check(card != NULL, return -EINVAL); + if ((err = snd_device_register_all(card)) < 0) + return err; + write_lock(&snd_card_rwlock); + if (snd_cards[card->number]) { + /* already registered */ + write_unlock(&snd_card_rwlock); + return 0; + } + if (card->id[0] == '\0') + choose_default_id(card); + snd_cards[card->number] = card; + write_unlock(&snd_card_rwlock); + if ((err = snd_info_card_register(card)) < 0) { + snd_printd("unable to create card info\n"); + goto __skip_info; + } + if ((entry = snd_info_create_card_entry(card, "id", card->proc_root)) == NULL) { + snd_printd("unable to create card entry\n"); + goto __skip_info; + } + entry->c.text.read_size = PAGE_SIZE; + entry->c.text.read = snd_card_id_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + card->proc_id = entry; + __skip_info: +#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER); +#endif + return 0; +} + +static snd_info_entry_t *snd_card_info_entry = NULL; + +static void snd_card_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + int idx, count; + snd_card_t *card; + + for (idx = count = 0; idx < SNDRV_CARDS; idx++) { + read_lock(&snd_card_rwlock); + if ((card = snd_cards[idx]) != NULL) { + count++; + snd_iprintf(buffer, "%i [%-15s]: %s - %s\n", + idx, + card->id, + card->driver, + card->shortname); + snd_iprintf(buffer, " %s\n", + card->longname); + } + read_unlock(&snd_card_rwlock); + } + if (!count) + snd_iprintf(buffer, "--- no soundcards ---\n"); +} + +#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS) + +void snd_card_info_read_oss(snd_info_buffer_t * buffer) +{ + int idx, count; + snd_card_t *card; + + for (idx = count = 0; idx < SNDRV_CARDS; idx++) { + read_lock(&snd_card_rwlock); + if ((card = snd_cards[idx]) != NULL) { + count++; + snd_iprintf(buffer, "%s\n", card->longname); + } + read_unlock(&snd_card_rwlock); + } + if (!count) { + snd_iprintf(buffer, "--- no soundcards ---\n"); + } +} + +#endif + +#ifdef MODULE +static snd_info_entry_t *snd_card_module_info_entry; +static void snd_card_module_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + int idx; + snd_card_t *card; + + for (idx = 0; idx < SNDRV_CARDS; idx++) { + read_lock(&snd_card_rwlock); + if ((card = snd_cards[idx]) != NULL) + snd_iprintf(buffer, "%i %s\n", idx, card->module->name); + read_unlock(&snd_card_rwlock); + } +} +#endif + +int __init snd_card_info_init(void) +{ + snd_info_entry_t *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "cards", NULL); + snd_runtime_check(entry != NULL, return -ENOMEM); + entry->c.text.read_size = PAGE_SIZE; + entry->c.text.read = snd_card_info_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + snd_card_info_entry = entry; + +#ifdef MODULE + entry = snd_info_create_module_entry(THIS_MODULE, "modules", NULL); + if (entry) { + entry->c.text.read_size = PAGE_SIZE; + entry->c.text.read = snd_card_module_info_read; + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); + else + snd_card_module_info_entry = entry; + } +#endif + + return 0; +} + +int __exit snd_card_info_done(void) +{ + if (snd_card_info_entry) + snd_info_unregister(snd_card_info_entry); +#ifdef MODULE + if (snd_card_module_info_entry) + snd_info_unregister(snd_card_module_info_entry); +#endif + return 0; +} + +/** + * snd_component_add - add a component string + * @card: soundcard structure + * @component: the component id string + * + * This function adds the component id string to the supported list. + * The component can be referred from the alsa-lib. + * + * Returns zero otherwise a negative error code. + */ + +int snd_component_add(snd_card_t *card, const char *component) +{ + char *ptr; + int len = strlen(component); + + ptr = strstr(card->components, component); + if (ptr != NULL) { + if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ + return 1; + } + if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) { + snd_BUG(); + return -ENOMEM; + } + if (card->components[0] != '\0') + strcat(card->components, " "); + strcat(card->components, component); + return 0; +} + +/** + * snd_card_file_add - add the file to the file list of the card + * @card: soundcard structure + * @file: file pointer + * + * This function adds the file to the file linked-list of the card. + * This linked-list is used to keep tracking the connection state, + * and to avoid the release of busy resources by hotplug. + * + * Returns zero or a negative error code. + */ +int snd_card_file_add(snd_card_t *card, struct file *file) +{ + struct snd_monitor_file *mfile; + + mfile = kmalloc(sizeof(*mfile), GFP_KERNEL); + if (mfile == NULL) + return -ENOMEM; + mfile->file = file; + mfile->next = NULL; + spin_lock(&card->files_lock); + if (card->shutdown) { + spin_unlock(&card->files_lock); + kfree(mfile); + return -ENODEV; + } + mfile->next = card->files; + card->files = mfile; + spin_unlock(&card->files_lock); + return 0; +} + +/** + * snd_card_file_remove - remove the file from the file list + * @card: soundcard structure + * @file: file pointer + * + * This function removes the file formerly added to the card via + * snd_card_file_add() function. + * If all files are removed and the release of the card is + * scheduled, it will wake up the the thread to call snd_card_free() + * (see snd_card_free_in_thread() function). + * + * Returns zero or a negative error code. + */ +int snd_card_file_remove(snd_card_t *card, struct file *file) +{ + struct snd_monitor_file *mfile, *pfile = NULL; + + spin_lock(&card->files_lock); + mfile = card->files; + while (mfile) { + if (mfile->file == file) { + if (pfile) + pfile->next = mfile->next; + else + card->files = mfile->next; + break; + } + pfile = mfile; + mfile = mfile->next; + } + spin_unlock(&card->files_lock); + if (card->files == NULL) + wake_up(&card->shutdown_sleep); + if (!mfile) { + snd_printk(KERN_ERR "ALSA card file remove problem (%p)\n", file); + return -ENOENT; + } + kfree(mfile); + return 0; +} + +#ifdef CONFIG_PM +/** + * snd_power_wait - wait until the power-state is changed. + * @card: soundcard structure + * @power_state: expected power state + * @file: file structure for the O_NONBLOCK check (optional) + * + * Waits until the power-state is changed. + * + * Note: the power lock must be active before call. + */ +int snd_power_wait(snd_card_t *card, unsigned int power_state, struct file *file) +{ + wait_queue_t wait; + int result = 0; + + /* fastpath */ + if (snd_power_get_state(card) == power_state) + return 0; + init_waitqueue_entry(&wait, current); + add_wait_queue(&card->power_sleep, &wait); + while (1) { + if (card->shutdown) { + result = -ENODEV; + break; + } + if (snd_power_get_state(card) == power_state) + break; +#if 0 /* block all devices */ + if (file && (file->f_flags & O_NONBLOCK)) { + result = -EAGAIN; + break; + } +#endif + set_current_state(TASK_UNINTERRUPTIBLE); + snd_power_unlock(card); + schedule_timeout(30 * HZ); + snd_power_lock(card); + } + remove_wait_queue(&card->power_sleep, &wait); + return result; +} + +/** + * snd_card_set_pm_callback - set the PCI power-management callbacks + * @card: soundcard structure + * @suspend: suspend callback function + * @resume: resume callback function + * @private_data: private data to pass to the callback functions + * + * Sets the power-management callback functions of the card. + * These callbacks are called from ALSA's common PCI suspend/resume + * handler and from the control API. + */ +int snd_card_set_pm_callback(snd_card_t *card, + int (*suspend)(snd_card_t *, pm_message_t), + int (*resume)(snd_card_t *), + void *private_data) +{ + card->pm_suspend = suspend; + card->pm_resume = resume; + card->pm_private_data = private_data; + return 0; +} + +#ifdef CONFIG_SND_GENERIC_PM +/* + * use platform_device for generic power-management without a proper bus + * (e.g. ISA) + */ +struct snd_generic_device { + struct platform_device pdev; + snd_card_t *card; +}; + +#define get_snd_generic_card(dev) container_of(to_platform_device(dev), struct snd_generic_device, pdev)->card + +#define SND_GENERIC_NAME "snd_generic_pm" + +static int snd_generic_suspend(struct device *dev, u32 state, u32 level); +static int snd_generic_resume(struct device *dev, u32 level); + +static struct device_driver snd_generic_driver = { + .name = SND_GENERIC_NAME, + .bus = &platform_bus_type, + .suspend = snd_generic_suspend, + .resume = snd_generic_resume, +}; + +static int generic_driver_registered; + +static void generic_driver_unregister(void) +{ + if (generic_driver_registered) { + generic_driver_registered--; + if (! generic_driver_registered) + driver_unregister(&snd_generic_driver); + } +} + +static struct snd_generic_device *snd_generic_device_register(snd_card_t *card) +{ + struct snd_generic_device *dev; + + if (! generic_driver_registered) { + if (driver_register(&snd_generic_driver) < 0) + return NULL; + } + generic_driver_registered++; + + dev = kcalloc(1, sizeof(*dev), GFP_KERNEL); + if (! dev) { + generic_driver_unregister(); + return NULL; + } + + dev->pdev.name = SND_GENERIC_NAME; + dev->pdev.id = card->number; + dev->card = card; + if (platform_device_register(&dev->pdev) < 0) { + kfree(dev); + generic_driver_unregister(); + return NULL; + } + return dev; +} + +static void snd_generic_device_unregister(struct snd_generic_device *dev) +{ + platform_device_unregister(&dev->pdev); + kfree(dev); + generic_driver_unregister(); +} + +/* suspend/resume callbacks for snd_generic platform device */ +static int snd_generic_suspend(struct device *dev, u32 state, u32 level) +{ + snd_card_t *card; + + if (level != SUSPEND_DISABLE) + return 0; + + card = get_snd_generic_card(dev); + if (card->power_state == SNDRV_CTL_POWER_D3hot) + return 0; + card->pm_suspend(card, PMSG_SUSPEND); + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + return 0; +} + +static int snd_generic_resume(struct device *dev, u32 level) +{ + snd_card_t *card; + + if (level != RESUME_ENABLE) + return 0; + + card = get_snd_generic_card(dev); + if (card->power_state == SNDRV_CTL_POWER_D0) + return 0; + card->pm_resume(card); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +/** + * snd_card_set_generic_pm_callback - set the generic power-management callbacks + * @card: soundcard structure + * @suspend: suspend callback function + * @resume: resume callback function + * @private_data: private data to pass to the callback functions + * + * Registers the power-management and sets the lowlevel callbacks for + * the given card. These callbacks are called from the ALSA's common + * PM handler and from the control API. + */ +int snd_card_set_generic_pm_callback(snd_card_t *card, + int (*suspend)(snd_card_t *, pm_message_t), + int (*resume)(snd_card_t *), + void *private_data) +{ + card->pm_dev = snd_generic_device_register(card); + if (! card->pm_dev) + return -ENOMEM; + snd_card_set_pm_callback(card, suspend, resume, private_data); + return 0; +} +#endif /* CONFIG_SND_GENERIC_PM */ + +#ifdef CONFIG_PCI +int snd_card_pci_suspend(struct pci_dev *dev, pm_message_t state) +{ + snd_card_t *card = pci_get_drvdata(dev); + int err; + if (! card || ! card->pm_suspend) + return 0; + if (card->power_state == SNDRV_CTL_POWER_D3hot) + return 0; + err = card->pm_suspend(card, PMSG_SUSPEND); + pci_save_state(dev); + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + return err; +} + +int snd_card_pci_resume(struct pci_dev *dev) +{ + snd_card_t *card = pci_get_drvdata(dev); + if (! card || ! card->pm_resume) + return 0; + if (card->power_state == SNDRV_CTL_POWER_D0) + return 0; + /* restore the PCI config space */ + pci_restore_state(dev); + card->pm_resume(card); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + +#endif /* CONFIG_PM */ diff --git a/sound/core/isadma.c b/sound/core/isadma.c new file mode 100644 index 000000000000..1a378951da5b --- /dev/null +++ b/sound/core/isadma.c @@ -0,0 +1,103 @@ +/* + * ISA DMA support functions + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Defining following add some delay. Maybe this helps for some broken + * ISA DMA controllers. + */ + +#undef HAVE_REALLY_SLOW_DMA_CONTROLLER + +#include <sound/driver.h> +#include <sound/core.h> +#include <asm/dma.h> + +/** + * snd_dma_program - program an ISA DMA transfer + * @dma: the dma number + * @addr: the physical address of the buffer + * @size: the DMA transfer size + * @mode: the DMA transfer mode, DMA_MODE_XXX + * + * Programs an ISA DMA transfer for the given buffer. + */ +void snd_dma_program(unsigned long dma, + unsigned long addr, unsigned int size, + unsigned short mode) +{ + unsigned long flags; + + flags = claim_dma_lock(); + disable_dma(dma); + clear_dma_ff(dma); + set_dma_mode(dma, mode); + set_dma_addr(dma, addr); + set_dma_count(dma, size); + if (!(mode & DMA_MODE_NO_ENABLE)) + enable_dma(dma); + release_dma_lock(flags); +} + +/** + * snd_dma_disable - stop the ISA DMA transfer + * @dma: the dma number + * + * Stops the ISA DMA transfer. + */ +void snd_dma_disable(unsigned long dma) +{ + unsigned long flags; + + flags = claim_dma_lock(); + clear_dma_ff(dma); + disable_dma(dma); + release_dma_lock(flags); +} + +/** + * snd_dma_pointer - return the current pointer to DMA transfer buffer in bytes + * @dma: the dma number + * @size: the dma transfer size + * + * Returns the current pointer in DMA tranfer buffer in bytes + */ +unsigned int snd_dma_pointer(unsigned long dma, unsigned int size) +{ + unsigned long flags; + unsigned int result; + + flags = claim_dma_lock(); + clear_dma_ff(dma); + if (!isa_dma_bridge_buggy) + disable_dma(dma); + result = get_dma_residue(dma); + if (!isa_dma_bridge_buggy) + enable_dma(dma); + release_dma_lock(flags); +#ifdef CONFIG_SND_DEBUG + if (result > size) + snd_printk(KERN_ERR "pointer (0x%x) for DMA #%ld is greater than transfer size (0x%x)\n", result, dma, size); +#endif + if (result >= size || result == 0) + return 0; + else + return size - result; +} diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c new file mode 100644 index 000000000000..344a83fd7c2e --- /dev/null +++ b/sound/core/memalloc.c @@ -0,0 +1,663 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Takashi Iwai <tiwai@suse.de> + * + * Generic memory allocators + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/proc_fs.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/dma-mapping.h> +#include <linux/moduleparam.h> +#include <asm/semaphore.h> +#include <sound/memalloc.h> +#ifdef CONFIG_SBUS +#include <asm/sbus.h> +#endif + + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Memory allocator for ALSA system."); +MODULE_LICENSE("GPL"); + + +#ifndef SNDRV_CARDS +#define SNDRV_CARDS 8 +#endif + +/* FIXME: so far only some PCI devices have the preallocation table */ +#ifdef CONFIG_PCI +static int enable[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable cards to allocate buffers."); +#endif + +/* + */ + +void *snd_malloc_sgbuf_pages(struct device *device, + size_t size, struct snd_dma_buffer *dmab, + size_t *res_size); +int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab); + +/* + */ + +static DECLARE_MUTEX(list_mutex); +static LIST_HEAD(mem_list_head); + +/* buffer preservation list */ +struct snd_mem_list { + struct snd_dma_buffer buffer; + unsigned int id; + struct list_head list; +}; + +/* id for pre-allocated buffers */ +#define SNDRV_DMA_DEVICE_UNUSED (unsigned int)-1 + +#ifdef CONFIG_SND_DEBUG +#define __ASTRING__(x) #x +#define snd_assert(expr, args...) do {\ + if (!(expr)) {\ + printk(KERN_ERR "snd-malloc: BUG? (%s) (called from %p)\n", __ASTRING__(expr), __builtin_return_address(0));\ + args;\ + }\ +} while (0) +#else +#define snd_assert(expr, args...) /**/ +#endif + +/* + * Hacks + */ + +#if defined(__i386__) || defined(__ppc__) || defined(__x86_64__) +/* + * A hack to allocate large buffers via dma_alloc_coherent() + * + * since dma_alloc_coherent always tries GFP_DMA when the requested + * pci memory region is below 32bit, it happens quite often that even + * 2 order of pages cannot be allocated. + * + * so in the following, we allocate at first without dma_mask, so that + * allocation will be done without GFP_DMA. if the area doesn't match + * with the requested region, then realloate with the original dma_mask + * again. + * + * Really, we want to move this type of thing into dma_alloc_coherent() + * so dma_mask doesn't have to be messed with. + */ + +static void *snd_dma_hack_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, int flags) +{ + void *ret; + u64 dma_mask, coherent_dma_mask; + + if (dev == NULL || !dev->dma_mask) + return dma_alloc_coherent(dev, size, dma_handle, flags); + dma_mask = *dev->dma_mask; + coherent_dma_mask = dev->coherent_dma_mask; + *dev->dma_mask = 0xffffffff; /* do without masking */ + dev->coherent_dma_mask = 0xffffffff; /* do without masking */ + ret = dma_alloc_coherent(dev, size, dma_handle, flags); + *dev->dma_mask = dma_mask; /* restore */ + dev->coherent_dma_mask = coherent_dma_mask; /* restore */ + if (ret) { + /* obtained address is out of range? */ + if (((unsigned long)*dma_handle + size - 1) & ~dma_mask) { + /* reallocate with the proper mask */ + dma_free_coherent(dev, size, ret, *dma_handle); + ret = dma_alloc_coherent(dev, size, dma_handle, flags); + } + } else { + /* wish to success now with the proper mask... */ + if (dma_mask != 0xffffffffUL) { + /* allocation with GFP_ATOMIC to avoid the long stall */ + flags &= ~GFP_KERNEL; + flags |= GFP_ATOMIC; + ret = dma_alloc_coherent(dev, size, dma_handle, flags); + } + } + return ret; +} + +/* redefine dma_alloc_coherent for some architectures */ +#undef dma_alloc_coherent +#define dma_alloc_coherent snd_dma_hack_alloc_coherent + +#endif /* arch */ + +#if ! defined(__arm__) +#define NEED_RESERVE_PAGES +#endif + +/* + * + * Generic memory allocators + * + */ + +static long snd_allocated_pages; /* holding the number of allocated pages */ + +static inline void inc_snd_pages(int order) +{ + snd_allocated_pages += 1 << order; +} + +static inline void dec_snd_pages(int order) +{ + snd_allocated_pages -= 1 << order; +} + +static void mark_pages(struct page *page, int order) +{ + struct page *last_page = page + (1 << order); + while (page < last_page) + SetPageReserved(page++); +} + +static void unmark_pages(struct page *page, int order) +{ + struct page *last_page = page + (1 << order); + while (page < last_page) + ClearPageReserved(page++); +} + +/** + * snd_malloc_pages - allocate pages with the given size + * @size: the size to allocate in bytes + * @gfp_flags: the allocation conditions, GFP_XXX + * + * Allocates the physically contiguous pages with the given size. + * + * Returns the pointer of the buffer, or NULL if no enoguh memory. + */ +void *snd_malloc_pages(size_t size, unsigned int gfp_flags) +{ + int pg; + void *res; + + snd_assert(size > 0, return NULL); + snd_assert(gfp_flags != 0, return NULL); + pg = get_order(size); + if ((res = (void *) __get_free_pages(gfp_flags, pg)) != NULL) { + mark_pages(virt_to_page(res), pg); + inc_snd_pages(pg); + } + return res; +} + +/** + * snd_free_pages - release the pages + * @ptr: the buffer pointer to release + * @size: the allocated buffer size + * + * Releases the buffer allocated via snd_malloc_pages(). + */ +void snd_free_pages(void *ptr, size_t size) +{ + int pg; + + if (ptr == NULL) + return; + pg = get_order(size); + dec_snd_pages(pg); + unmark_pages(virt_to_page(ptr), pg); + free_pages((unsigned long) ptr, pg); +} + +/* + * + * Bus-specific memory allocators + * + */ + +/* allocate the coherent DMA pages */ +static void *snd_malloc_dev_pages(struct device *dev, size_t size, dma_addr_t *dma) +{ + int pg; + void *res; + unsigned int gfp_flags; + + snd_assert(size > 0, return NULL); + snd_assert(dma != NULL, return NULL); + pg = get_order(size); + gfp_flags = GFP_KERNEL + | __GFP_NORETRY /* don't trigger OOM-killer */ + | __GFP_NOWARN; /* no stack trace print - this call is non-critical */ + res = dma_alloc_coherent(dev, PAGE_SIZE << pg, dma, gfp_flags); + if (res != NULL) { +#ifdef NEED_RESERVE_PAGES + mark_pages(virt_to_page(res), pg); /* should be dma_to_page() */ +#endif + inc_snd_pages(pg); + } + + return res; +} + +/* free the coherent DMA pages */ +static void snd_free_dev_pages(struct device *dev, size_t size, void *ptr, + dma_addr_t dma) +{ + int pg; + + if (ptr == NULL) + return; + pg = get_order(size); + dec_snd_pages(pg); +#ifdef NEED_RESERVE_PAGES + unmark_pages(virt_to_page(ptr), pg); /* should be dma_to_page() */ +#endif + dma_free_coherent(dev, PAGE_SIZE << pg, ptr, dma); +} + +#ifdef CONFIG_SBUS + +static void *snd_malloc_sbus_pages(struct device *dev, size_t size, + dma_addr_t *dma_addr) +{ + struct sbus_dev *sdev = (struct sbus_dev *)dev; + int pg; + void *res; + + snd_assert(size > 0, return NULL); + snd_assert(dma_addr != NULL, return NULL); + pg = get_order(size); + res = sbus_alloc_consistent(sdev, PAGE_SIZE * (1 << pg), dma_addr); + if (res != NULL) + inc_snd_pages(pg); + return res; +} + +static void snd_free_sbus_pages(struct device *dev, size_t size, + void *ptr, dma_addr_t dma_addr) +{ + struct sbus_dev *sdev = (struct sbus_dev *)dev; + int pg; + + if (ptr == NULL) + return; + pg = get_order(size); + dec_snd_pages(pg); + sbus_free_consistent(sdev, PAGE_SIZE * (1 << pg), ptr, dma_addr); +} + +#endif /* CONFIG_SBUS */ + +/* + * + * ALSA generic memory management + * + */ + + +/** + * snd_dma_alloc_pages - allocate the buffer area according to the given type + * @type: the DMA buffer type + * @device: the device pointer + * @size: the buffer size to allocate + * @dmab: buffer allocation record to store the allocated data + * + * Calls the memory-allocator function for the corresponding + * buffer type. + * + * Returns zero if the buffer with the given size is allocated successfuly, + * other a negative value at error. + */ +int snd_dma_alloc_pages(int type, struct device *device, size_t size, + struct snd_dma_buffer *dmab) +{ + snd_assert(size > 0, return -ENXIO); + snd_assert(dmab != NULL, return -ENXIO); + + dmab->dev.type = type; + dmab->dev.dev = device; + dmab->bytes = 0; + switch (type) { + case SNDRV_DMA_TYPE_CONTINUOUS: + dmab->area = snd_malloc_pages(size, (unsigned long)device); + dmab->addr = 0; + break; +#ifdef CONFIG_SBUS + case SNDRV_DMA_TYPE_SBUS: + dmab->area = snd_malloc_sbus_pages(device, size, &dmab->addr); + break; +#endif + case SNDRV_DMA_TYPE_DEV: + dmab->area = snd_malloc_dev_pages(device, size, &dmab->addr); + break; + case SNDRV_DMA_TYPE_DEV_SG: + snd_malloc_sgbuf_pages(device, size, dmab, NULL); + break; + default: + printk(KERN_ERR "snd-malloc: invalid device type %d\n", type); + dmab->area = NULL; + dmab->addr = 0; + return -ENXIO; + } + if (! dmab->area) + return -ENOMEM; + dmab->bytes = size; + return 0; +} + +/** + * snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback + * @type: the DMA buffer type + * @device: the device pointer + * @size: the buffer size to allocate + * @dmab: buffer allocation record to store the allocated data + * + * Calls the memory-allocator function for the corresponding + * buffer type. When no space is left, this function reduces the size and + * tries to allocate again. The size actually allocated is stored in + * res_size argument. + * + * Returns zero if the buffer with the given size is allocated successfuly, + * other a negative value at error. + */ +int snd_dma_alloc_pages_fallback(int type, struct device *device, size_t size, + struct snd_dma_buffer *dmab) +{ + int err; + + snd_assert(size > 0, return -ENXIO); + snd_assert(dmab != NULL, return -ENXIO); + + while ((err = snd_dma_alloc_pages(type, device, size, dmab)) < 0) { + if (err != -ENOMEM) + return err; + size >>= 1; + if (size <= PAGE_SIZE) + return -ENOMEM; + } + if (! dmab->area) + return -ENOMEM; + return 0; +} + + +/** + * snd_dma_free_pages - release the allocated buffer + * @dmab: the buffer allocation record to release + * + * Releases the allocated buffer via snd_dma_alloc_pages(). + */ +void snd_dma_free_pages(struct snd_dma_buffer *dmab) +{ + switch (dmab->dev.type) { + case SNDRV_DMA_TYPE_CONTINUOUS: + snd_free_pages(dmab->area, dmab->bytes); + break; +#ifdef CONFIG_SBUS + case SNDRV_DMA_TYPE_SBUS: + snd_free_sbus_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr); + break; +#endif + case SNDRV_DMA_TYPE_DEV: + snd_free_dev_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr); + break; + case SNDRV_DMA_TYPE_DEV_SG: + snd_free_sgbuf_pages(dmab); + break; + default: + printk(KERN_ERR "snd-malloc: invalid device type %d\n", dmab->dev.type); + } +} + + +/** + * snd_dma_get_reserved - get the reserved buffer for the given device + * @dmab: the buffer allocation record to store + * @id: the buffer id + * + * Looks for the reserved-buffer list and re-uses if the same buffer + * is found in the list. When the buffer is found, it's removed from the free list. + * + * Returns the size of buffer if the buffer is found, or zero if not found. + */ +size_t snd_dma_get_reserved_buf(struct snd_dma_buffer *dmab, unsigned int id) +{ + struct list_head *p; + struct snd_mem_list *mem; + + snd_assert(dmab, return 0); + + down(&list_mutex); + list_for_each(p, &mem_list_head) { + mem = list_entry(p, struct snd_mem_list, list); + if (mem->id == id && + ! memcmp(&mem->buffer.dev, &dmab->dev, sizeof(dmab->dev))) { + list_del(p); + *dmab = mem->buffer; + kfree(mem); + up(&list_mutex); + return dmab->bytes; + } + } + up(&list_mutex); + return 0; +} + +/** + * snd_dma_reserve_buf - reserve the buffer + * @dmab: the buffer to reserve + * @id: the buffer id + * + * Reserves the given buffer as a reserved buffer. + * + * Returns zero if successful, or a negative code at error. + */ +int snd_dma_reserve_buf(struct snd_dma_buffer *dmab, unsigned int id) +{ + struct snd_mem_list *mem; + + snd_assert(dmab, return -EINVAL); + mem = kmalloc(sizeof(*mem), GFP_KERNEL); + if (! mem) + return -ENOMEM; + down(&list_mutex); + mem->buffer = *dmab; + mem->id = id; + list_add_tail(&mem->list, &mem_list_head); + up(&list_mutex); + return 0; +} + +/* + * purge all reserved buffers + */ +static void free_all_reserved_pages(void) +{ + struct list_head *p; + struct snd_mem_list *mem; + + down(&list_mutex); + while (! list_empty(&mem_list_head)) { + p = mem_list_head.next; + mem = list_entry(p, struct snd_mem_list, list); + list_del(p); + snd_dma_free_pages(&mem->buffer); + kfree(mem); + } + up(&list_mutex); +} + + + +/* + * allocation of buffers for pre-defined devices + */ + +#ifdef CONFIG_PCI +/* FIXME: for pci only - other bus? */ +struct prealloc_dev { + unsigned short vendor; + unsigned short device; + unsigned long dma_mask; + unsigned int size; + unsigned int buffers; +}; + +#define HAMMERFALL_BUFFER_SIZE (16*1024*4*(26+1)+0x10000) + +static struct prealloc_dev prealloc_devices[] __initdata = { + { + /* hammerfall */ + .vendor = 0x10ee, + .device = 0x3fc4, + .dma_mask = 0xffffffff, + .size = HAMMERFALL_BUFFER_SIZE, + .buffers = 2 + }, + { + /* HDSP */ + .vendor = 0x10ee, + .device = 0x3fc5, + .dma_mask = 0xffffffff, + .size = HAMMERFALL_BUFFER_SIZE, + .buffers = 2 + }, + { }, /* terminator */ +}; + +static void __init preallocate_cards(void) +{ + struct pci_dev *pci = NULL; + int card; + + card = 0; + + while ((pci = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pci)) != NULL) { + struct prealloc_dev *dev; + unsigned int i; + if (card >= SNDRV_CARDS) + break; + for (dev = prealloc_devices; dev->vendor; dev++) { + if (dev->vendor == pci->vendor && dev->device == pci->device) + break; + } + if (! dev->vendor) + continue; + if (! enable[card++]) { + printk(KERN_DEBUG "snd-page-alloc: skipping card %d, device %04x:%04x\n", card, pci->vendor, pci->device); + continue; + } + + if (pci_set_dma_mask(pci, dev->dma_mask) < 0 || + pci_set_consistent_dma_mask(pci, dev->dma_mask) < 0) { + printk(KERN_ERR "snd-page-alloc: cannot set DMA mask %lx for pci %04x:%04x\n", dev->dma_mask, dev->vendor, dev->device); + continue; + } + for (i = 0; i < dev->buffers; i++) { + struct snd_dma_buffer dmab; + memset(&dmab, 0, sizeof(dmab)); + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(pci), + dev->size, &dmab) < 0) + printk(KERN_WARNING "snd-page-alloc: cannot allocate buffer pages (size = %d)\n", dev->size); + else + snd_dma_reserve_buf(&dmab, snd_dma_pci_buf_id(pci)); + } + } +} +#else +#define preallocate_cards() /* NOP */ +#endif + + +#ifdef CONFIG_PROC_FS +/* + * proc file interface + */ +static int snd_mem_proc_read(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + long pages = snd_allocated_pages >> (PAGE_SHIFT-12); + struct list_head *p; + struct snd_mem_list *mem; + int devno; + static char *types[] = { "UNKNOWN", "CONT", "DEV", "DEV-SG", "SBUS" }; + + down(&list_mutex); + len += snprintf(page + len, count - len, + "pages : %li bytes (%li pages per %likB)\n", + pages * PAGE_SIZE, pages, PAGE_SIZE / 1024); + devno = 0; + list_for_each(p, &mem_list_head) { + mem = list_entry(p, struct snd_mem_list, list); + devno++; + len += snprintf(page + len, count - len, + "buffer %d : ID %08x : type %s\n", + devno, mem->id, types[mem->buffer.dev.type]); + len += snprintf(page + len, count - len, + " addr = 0x%lx, size = %d bytes\n", + (unsigned long)mem->buffer.addr, (int)mem->buffer.bytes); + } + up(&list_mutex); + return len; +} +#endif /* CONFIG_PROC_FS */ + +/* + * module entry + */ + +static int __init snd_mem_init(void) +{ +#ifdef CONFIG_PROC_FS + create_proc_read_entry("driver/snd-page-alloc", 0, NULL, snd_mem_proc_read, NULL); +#endif + preallocate_cards(); + return 0; +} + +static void __exit snd_mem_exit(void) +{ + remove_proc_entry("driver/snd-page-alloc", NULL); + free_all_reserved_pages(); + if (snd_allocated_pages > 0) + printk(KERN_ERR "snd-malloc: Memory leak? pages not freed = %li\n", snd_allocated_pages); +} + + +module_init(snd_mem_init) +module_exit(snd_mem_exit) + + +/* + * exports + */ +EXPORT_SYMBOL(snd_dma_alloc_pages); +EXPORT_SYMBOL(snd_dma_alloc_pages_fallback); +EXPORT_SYMBOL(snd_dma_free_pages); + +EXPORT_SYMBOL(snd_dma_get_reserved_buf); +EXPORT_SYMBOL(snd_dma_reserve_buf); + +EXPORT_SYMBOL(snd_malloc_pages); +EXPORT_SYMBOL(snd_free_pages); diff --git a/sound/core/memory.c b/sound/core/memory.c new file mode 100644 index 000000000000..20860fec9364 --- /dev/null +++ b/sound/core/memory.c @@ -0,0 +1,306 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * Memory allocation helpers. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/pci.h> +#include <sound/core.h> +#include <sound/info.h> + +/* + * memory allocation helpers and debug routines + */ + +#ifdef CONFIG_SND_DEBUG_MEMORY + +struct snd_alloc_track { + unsigned long magic; + void *caller; + size_t size; + struct list_head list; + long data[0]; +}; + +#define snd_alloc_track_entry(obj) (struct snd_alloc_track *)((char*)obj - (unsigned long)((struct snd_alloc_track *)0)->data) + +static long snd_alloc_kmalloc; +static long snd_alloc_vmalloc; +static LIST_HEAD(snd_alloc_kmalloc_list); +static LIST_HEAD(snd_alloc_vmalloc_list); +static DEFINE_SPINLOCK(snd_alloc_kmalloc_lock); +static DEFINE_SPINLOCK(snd_alloc_vmalloc_lock); +#define KMALLOC_MAGIC 0x87654321 +#define VMALLOC_MAGIC 0x87654320 +static snd_info_entry_t *snd_memory_info_entry; + +void snd_memory_init(void) +{ + snd_alloc_kmalloc = 0; + snd_alloc_vmalloc = 0; +} + +void snd_memory_done(void) +{ + struct list_head *head; + struct snd_alloc_track *t; + + if (snd_alloc_kmalloc > 0) + snd_printk(KERN_ERR "Not freed snd_alloc_kmalloc = %li\n", snd_alloc_kmalloc); + if (snd_alloc_vmalloc > 0) + snd_printk(KERN_ERR "Not freed snd_alloc_vmalloc = %li\n", snd_alloc_vmalloc); + list_for_each_prev(head, &snd_alloc_kmalloc_list) { + t = list_entry(head, struct snd_alloc_track, list); + if (t->magic != KMALLOC_MAGIC) { + snd_printk(KERN_ERR "Corrupted kmalloc\n"); + break; + } + snd_printk(KERN_ERR "kmalloc(%ld) from %p not freed\n", (long) t->size, t->caller); + } + list_for_each_prev(head, &snd_alloc_vmalloc_list) { + t = list_entry(head, struct snd_alloc_track, list); + if (t->magic != VMALLOC_MAGIC) { + snd_printk(KERN_ERR "Corrupted vmalloc\n"); + break; + } + snd_printk(KERN_ERR "vmalloc(%ld) from %p not freed\n", (long) t->size, t->caller); + } +} + +static void *__snd_kmalloc(size_t size, int flags, void *caller) +{ + unsigned long cpu_flags; + struct snd_alloc_track *t; + void *ptr; + + ptr = snd_wrapper_kmalloc(size + sizeof(struct snd_alloc_track), flags); + if (ptr != NULL) { + t = (struct snd_alloc_track *)ptr; + t->magic = KMALLOC_MAGIC; + t->caller = caller; + spin_lock_irqsave(&snd_alloc_kmalloc_lock, cpu_flags); + list_add_tail(&t->list, &snd_alloc_kmalloc_list); + spin_unlock_irqrestore(&snd_alloc_kmalloc_lock, cpu_flags); + t->size = size; + snd_alloc_kmalloc += size; + ptr = t->data; + } + return ptr; +} + +#define _snd_kmalloc(size, flags) __snd_kmalloc((size), (flags), __builtin_return_address(0)); +void *snd_hidden_kmalloc(size_t size, int flags) +{ + return _snd_kmalloc(size, flags); +} + +void *snd_hidden_kcalloc(size_t n, size_t size, int flags) +{ + void *ret = NULL; + if (n != 0 && size > INT_MAX / n) + return ret; + ret = _snd_kmalloc(n * size, flags); + if (ret) + memset(ret, 0, n * size); + return ret; +} + +void snd_hidden_kfree(const void *obj) +{ + unsigned long flags; + struct snd_alloc_track *t; + if (obj == NULL) + return; + t = snd_alloc_track_entry(obj); + if (t->magic != KMALLOC_MAGIC) { + snd_printk(KERN_WARNING "bad kfree (called from %p)\n", __builtin_return_address(0)); + return; + } + spin_lock_irqsave(&snd_alloc_kmalloc_lock, flags); + list_del(&t->list); + spin_unlock_irqrestore(&snd_alloc_kmalloc_lock, flags); + t->magic = 0; + snd_alloc_kmalloc -= t->size; + obj = t; + snd_wrapper_kfree(obj); +} + +void *snd_hidden_vmalloc(unsigned long size) +{ + void *ptr; + ptr = snd_wrapper_vmalloc(size + sizeof(struct snd_alloc_track)); + if (ptr) { + struct snd_alloc_track *t = (struct snd_alloc_track *)ptr; + t->magic = VMALLOC_MAGIC; + t->caller = __builtin_return_address(0); + spin_lock(&snd_alloc_vmalloc_lock); + list_add_tail(&t->list, &snd_alloc_vmalloc_list); + spin_unlock(&snd_alloc_vmalloc_lock); + t->size = size; + snd_alloc_vmalloc += size; + ptr = t->data; + } + return ptr; +} + +void snd_hidden_vfree(void *obj) +{ + struct snd_alloc_track *t; + if (obj == NULL) + return; + t = snd_alloc_track_entry(obj); + if (t->magic != VMALLOC_MAGIC) { + snd_printk(KERN_ERR "bad vfree (called from %p)\n", __builtin_return_address(0)); + return; + } + spin_lock(&snd_alloc_vmalloc_lock); + list_del(&t->list); + spin_unlock(&snd_alloc_vmalloc_lock); + t->magic = 0; + snd_alloc_vmalloc -= t->size; + obj = t; + snd_wrapper_vfree(obj); +} + +static void snd_memory_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + snd_iprintf(buffer, "kmalloc: %li bytes\n", snd_alloc_kmalloc); + snd_iprintf(buffer, "vmalloc: %li bytes\n", snd_alloc_vmalloc); +} + +int __init snd_memory_info_init(void) +{ + snd_info_entry_t *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "meminfo", NULL); + if (entry) { + entry->c.text.read_size = 256; + entry->c.text.read = snd_memory_info_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_memory_info_entry = entry; + return 0; +} + +int __exit snd_memory_info_done(void) +{ + if (snd_memory_info_entry) + snd_info_unregister(snd_memory_info_entry); + return 0; +} + +#else + +#define _snd_kmalloc kmalloc + +#endif /* CONFIG_SND_DEBUG_MEMORY */ + +/** + * snd_kmalloc_strdup - copy the string + * @string: the original string + * @flags: allocation conditions, GFP_XXX + * + * Allocates a memory chunk via kmalloc() and copies the string to it. + * + * Returns the pointer, or NULL if no enoguh memory. + */ +char *snd_kmalloc_strdup(const char *string, int flags) +{ + size_t len; + char *ptr; + + if (!string) + return NULL; + len = strlen(string) + 1; + ptr = _snd_kmalloc(len, flags); + if (ptr) + memcpy(ptr, string, len); + return ptr; +} + +/** + * copy_to_user_fromio - copy data from mmio-space to user-space + * @dst: the destination pointer on user-space + * @src: the source pointer on mmio + * @count: the data size to copy in bytes + * + * Copies the data from mmio-space to user-space. + * + * Returns zero if successful, or non-zero on failure. + */ +int copy_to_user_fromio(void __user *dst, const volatile void __iomem *src, size_t count) +{ +#if defined(__i386__) || defined(CONFIG_SPARC32) + return copy_to_user(dst, (const void*)src, count) ? -EFAULT : 0; +#else + char buf[256]; + while (count) { + size_t c = count; + if (c > sizeof(buf)) + c = sizeof(buf); + memcpy_fromio(buf, (void __iomem *)src, c); + if (copy_to_user(dst, buf, c)) + return -EFAULT; + count -= c; + dst += c; + src += c; + } + return 0; +#endif +} + +/** + * copy_from_user_toio - copy data from user-space to mmio-space + * @dst: the destination pointer on mmio-space + * @src: the source pointer on user-space + * @count: the data size to copy in bytes + * + * Copies the data from user-space to mmio-space. + * + * Returns zero if successful, or non-zero on failure. + */ +int copy_from_user_toio(volatile void __iomem *dst, const void __user *src, size_t count) +{ +#if defined(__i386__) || defined(CONFIG_SPARC32) + return copy_from_user((void*)dst, src, count) ? -EFAULT : 0; +#else + char buf[256]; + while (count) { + size_t c = count; + if (c > sizeof(buf)) + c = sizeof(buf); + if (copy_from_user(buf, src, c)) + return -EFAULT; + memcpy_toio(dst, buf, c); + count -= c; + dst += c; + src += c; + } + return 0; +#endif +} diff --git a/sound/core/misc.c b/sound/core/misc.c new file mode 100644 index 000000000000..1a81fe4df218 --- /dev/null +++ b/sound/core/misc.c @@ -0,0 +1,76 @@ +/* + * Misc and compatibility things + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <sound/core.h> + +int snd_task_name(struct task_struct *task, char *name, size_t size) +{ + unsigned int idx; + + snd_assert(task != NULL && name != NULL && size >= 2, return -EINVAL); + for (idx = 0; idx < sizeof(task->comm) && idx + 1 < size; idx++) + name[idx] = task->comm[idx]; + name[idx] = '\0'; + return 0; +} + +#ifdef CONFIG_SND_VERBOSE_PRINTK +void snd_verbose_printk(const char *file, int line, const char *format, ...) +{ + va_list args; + + if (format[0] == '<' && format[1] >= '0' && format[1] <= '9' && format[2] == '>') { + char tmp[] = "<0>"; + tmp[1] = format[1]; + printk("%sALSA %s:%d: ", tmp, file, line); + format += 3; + } else { + printk("ALSA %s:%d: ", file, line); + } + va_start(args, format); + vprintk(format, args); + va_end(args); +} +#endif + +#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_VERBOSE_PRINTK) +void snd_verbose_printd(const char *file, int line, const char *format, ...) +{ + va_list args; + + if (format[0] == '<' && format[1] >= '0' && format[1] <= '9' && format[2] == '>') { + char tmp[] = "<0>"; + tmp[1] = format[1]; + printk("%sALSA %s:%d: ", tmp, file, line); + format += 3; + } else { + printk(KERN_DEBUG "ALSA %s:%d: ", file, line); + } + va_start(args, format); + vprintk(format, args); + va_end(args); + +} +#endif diff --git a/sound/core/oss/Makefile b/sound/core/oss/Makefile new file mode 100644 index 000000000000..e6d5a045ba27 --- /dev/null +++ b/sound/core/oss/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> +# + +snd-mixer-oss-objs := mixer_oss.o + +snd-pcm-oss-objs := pcm_oss.o pcm_plugin.o \ + io.o copy.o linear.o mulaw.o route.o rate.o + +obj-$(CONFIG_SND_MIXER_OSS) += snd-mixer-oss.o +obj-$(CONFIG_SND_PCM_OSS) += snd-pcm-oss.o diff --git a/sound/core/oss/copy.c b/sound/core/oss/copy.c new file mode 100644 index 000000000000..edecbe7417bd --- /dev/null +++ b/sound/core/oss/copy.c @@ -0,0 +1,87 @@ +/* + * Linear conversion Plug-In + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "pcm_plugin.h" + +static snd_pcm_sframes_t copy_transfer(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames) +{ + unsigned int channel; + unsigned int nchannels; + + snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO); + if (frames == 0) + return 0; + nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; channel++) { + snd_assert(src_channels->area.first % 8 == 0 && + src_channels->area.step % 8 == 0, + return -ENXIO); + snd_assert(dst_channels->area.first % 8 == 0 && + dst_channels->area.step % 8 == 0, + return -ENXIO); + if (!src_channels->enabled) { + if (dst_channels->wanted) + snd_pcm_area_silence(&dst_channels->area, 0, frames, plugin->dst_format.format); + dst_channels->enabled = 0; + continue; + } + dst_channels->enabled = 1; + snd_pcm_area_copy(&src_channels->area, 0, &dst_channels->area, 0, frames, plugin->src_format.format); + src_channels++; + dst_channels++; + } + return frames; +} + +int snd_pcm_plugin_build_copy(snd_pcm_plug_t *plug, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + snd_pcm_plugin_t **r_plugin) +{ + int err; + snd_pcm_plugin_t *plugin; + int width; + + snd_assert(r_plugin != NULL, return -ENXIO); + *r_plugin = NULL; + + snd_assert(src_format->format == dst_format->format, return -ENXIO); + snd_assert(src_format->rate == dst_format->rate, return -ENXIO); + snd_assert(src_format->channels == dst_format->channels, return -ENXIO); + + width = snd_pcm_format_physical_width(src_format->format); + snd_assert(width > 0, return -ENXIO); + + err = snd_pcm_plugin_build(plug, "copy", src_format, dst_format, + 0, &plugin); + if (err < 0) + return err; + plugin->transfer = copy_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/io.c b/sound/core/oss/io.c new file mode 100644 index 000000000000..bb1c99a5b734 --- /dev/null +++ b/sound/core/oss/io.c @@ -0,0 +1,134 @@ +/* + * PCM I/O Plug-In Interface + * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "pcm_plugin.h" + +#define pcm_write(plug,buf,count) snd_pcm_oss_write3(plug,buf,count,1) +#define pcm_writev(plug,vec,count) snd_pcm_oss_writev3(plug,vec,count,1) +#define pcm_read(plug,buf,count) snd_pcm_oss_read3(plug,buf,count,1) +#define pcm_readv(plug,vec,count) snd_pcm_oss_readv3(plug,vec,count,1) + +/* + * Basic io plugin + */ + +static snd_pcm_sframes_t io_playback_transfer(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels ATTRIBUTE_UNUSED, + snd_pcm_uframes_t frames) +{ + snd_assert(plugin != NULL, return -ENXIO); + snd_assert(src_channels != NULL, return -ENXIO); + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + return pcm_write(plugin->plug, src_channels->area.addr, frames); + } else { + int channel, channels = plugin->dst_format.channels; + void **bufs = (void**)plugin->extra_data; + snd_assert(bufs != NULL, return -ENXIO); + for (channel = 0; channel < channels; channel++) { + if (src_channels[channel].enabled) + bufs[channel] = src_channels[channel].area.addr; + else + bufs[channel] = NULL; + } + return pcm_writev(plugin->plug, bufs, frames); + } +} + +static snd_pcm_sframes_t io_capture_transfer(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels ATTRIBUTE_UNUSED, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames) +{ + snd_assert(plugin != NULL, return -ENXIO); + snd_assert(dst_channels != NULL, return -ENXIO); + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + return pcm_read(plugin->plug, dst_channels->area.addr, frames); + } else { + int channel, channels = plugin->dst_format.channels; + void **bufs = (void**)plugin->extra_data; + snd_assert(bufs != NULL, return -ENXIO); + for (channel = 0; channel < channels; channel++) { + if (dst_channels[channel].enabled) + bufs[channel] = dst_channels[channel].area.addr; + else + bufs[channel] = NULL; + } + return pcm_readv(plugin->plug, bufs, frames); + } + return 0; +} + +static snd_pcm_sframes_t io_src_channels(snd_pcm_plugin_t *plugin, + snd_pcm_uframes_t frames, + snd_pcm_plugin_channel_t **channels) +{ + int err; + unsigned int channel; + snd_pcm_plugin_channel_t *v; + err = snd_pcm_plugin_client_channels(plugin, frames, &v); + if (err < 0) + return err; + *channels = v; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + for (channel = 0; channel < plugin->src_format.channels; ++channel, ++v) + v->wanted = 1; + } + return frames; +} + +int snd_pcm_plugin_build_io(snd_pcm_plug_t *plug, + snd_pcm_hw_params_t *params, + snd_pcm_plugin_t **r_plugin) +{ + int err; + snd_pcm_plugin_format_t format; + snd_pcm_plugin_t *plugin; + + snd_assert(r_plugin != NULL, return -ENXIO); + *r_plugin = NULL; + snd_assert(plug != NULL && params != NULL, return -ENXIO); + format.format = params_format(params); + format.rate = params_rate(params); + format.channels = params_channels(params); + err = snd_pcm_plugin_build(plug, "I/O io", + &format, &format, + sizeof(void *) * format.channels, + &plugin); + if (err < 0) + return err; + plugin->access = params_access(params); + if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) { + plugin->transfer = io_playback_transfer; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) + plugin->client_channels = io_src_channels; + } else { + plugin->transfer = io_capture_transfer; + } + + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/linear.c b/sound/core/oss/linear.c new file mode 100644 index 000000000000..12ed27a57b27 --- /dev/null +++ b/sound/core/oss/linear.c @@ -0,0 +1,158 @@ +/* + * Linear conversion Plug-In + * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>, + * Abramo Bagnara <abramo@alsa-project.org> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "pcm_plugin.h" + +/* + * Basic linear conversion plugin + */ + +typedef struct linear_private_data { + int conv; +} linear_t; + +static void convert(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames) +{ +#define CONV_LABELS +#include "plugin_ops.h" +#undef CONV_LABELS + linear_t *data = (linear_t *)plugin->extra_data; + void *conv = conv_labels[data->conv]; + int channel; + int nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; ++channel) { + char *src; + char *dst; + int src_step, dst_step; + snd_pcm_uframes_t frames1; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + frames1 = frames; + while (frames1-- > 0) { + goto *conv; +#define CONV_END after +#include "plugin_ops.h" +#undef CONV_END + after: + src += src_step; + dst += dst_step; + } + } +} + +static snd_pcm_sframes_t linear_transfer(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames) +{ + linear_t *data; + + snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO); + data = (linear_t *)plugin->extra_data; + if (frames == 0) + return 0; +#ifdef CONFIG_SND_DEBUG + { + unsigned int channel; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + snd_assert(src_channels[channel].area.first % 8 == 0 && + src_channels[channel].area.step % 8 == 0, + return -ENXIO); + snd_assert(dst_channels[channel].area.first % 8 == 0 && + dst_channels[channel].area.step % 8 == 0, + return -ENXIO); + } + } +#endif + convert(plugin, src_channels, dst_channels, frames); + return frames; +} + +int conv_index(int src_format, int dst_format) +{ + int src_endian, dst_endian, sign, src_width, dst_width; + + sign = (snd_pcm_format_signed(src_format) != + snd_pcm_format_signed(dst_format)); +#ifdef SNDRV_LITTLE_ENDIAN + src_endian = snd_pcm_format_big_endian(src_format); + dst_endian = snd_pcm_format_big_endian(dst_format); +#else + src_endian = snd_pcm_format_little_endian(src_format); + dst_endian = snd_pcm_format_little_endian(dst_format); +#endif + + if (src_endian < 0) + src_endian = 0; + if (dst_endian < 0) + dst_endian = 0; + + src_width = snd_pcm_format_width(src_format) / 8 - 1; + dst_width = snd_pcm_format_width(dst_format) / 8 - 1; + + return src_width * 32 + src_endian * 16 + sign * 8 + dst_width * 2 + dst_endian; +} + +int snd_pcm_plugin_build_linear(snd_pcm_plug_t *plug, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + snd_pcm_plugin_t **r_plugin) +{ + int err; + struct linear_private_data *data; + snd_pcm_plugin_t *plugin; + + snd_assert(r_plugin != NULL, return -ENXIO); + *r_plugin = NULL; + + snd_assert(src_format->rate == dst_format->rate, return -ENXIO); + snd_assert(src_format->channels == dst_format->channels, return -ENXIO); + snd_assert(snd_pcm_format_linear(src_format->format) && + snd_pcm_format_linear(dst_format->format), return -ENXIO); + + err = snd_pcm_plugin_build(plug, "linear format conversion", + src_format, dst_format, + sizeof(linear_t), &plugin); + if (err < 0) + return err; + data = (linear_t *)plugin->extra_data; + data->conv = conv_index(src_format->format, dst_format->format); + plugin->transfer = linear_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c new file mode 100644 index 000000000000..98ed9a9f0da6 --- /dev/null +++ b/sound/core/oss/mixer_oss.c @@ -0,0 +1,1340 @@ +/* + * OSS emulation layer for the mixer interface + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/control.h> +#include <sound/info.h> +#include <sound/mixer_oss.h> +#include <linux/soundcard.h> + +#define OSS_ALSAEMULVER _SIOR ('M', 249, int) + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Mixer OSS emulation for ALSA."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MIXER); + +static int snd_mixer_oss_open(struct inode *inode, struct file *file) +{ + int cardnum = SNDRV_MINOR_OSS_CARD(iminor(inode)); + snd_card_t *card; + snd_mixer_oss_file_t *fmixer; + int err; + + if ((card = snd_cards[cardnum]) == NULL) + return -ENODEV; + if (card->mixer_oss == NULL) + return -ENODEV; + err = snd_card_file_add(card, file); + if (err < 0) + return err; + fmixer = kcalloc(1, sizeof(*fmixer), GFP_KERNEL); + if (fmixer == NULL) { + snd_card_file_remove(card, file); + return -ENOMEM; + } + fmixer->card = card; + fmixer->mixer = card->mixer_oss; + file->private_data = fmixer; + if (!try_module_get(card->module)) { + kfree(fmixer); + snd_card_file_remove(card, file); + return -EFAULT; + } + return 0; +} + +static int snd_mixer_oss_release(struct inode *inode, struct file *file) +{ + snd_mixer_oss_file_t *fmixer; + + if (file->private_data) { + fmixer = (snd_mixer_oss_file_t *) file->private_data; + module_put(fmixer->card->module); + snd_card_file_remove(fmixer->card, file); + kfree(fmixer); + } + return 0; +} + +static int snd_mixer_oss_info(snd_mixer_oss_file_t *fmixer, + mixer_info __user *_info) +{ + snd_card_t *card = fmixer->card; + snd_mixer_oss_t *mixer = fmixer->mixer; + struct mixer_info info; + + memset(&info, 0, sizeof(info)); + strlcpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id)); + strlcpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name)); + info.modify_counter = card->mixer_oss_change_count; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_mixer_oss_info_obsolete(snd_mixer_oss_file_t *fmixer, + _old_mixer_info __user *_info) +{ + snd_card_t *card = fmixer->card; + snd_mixer_oss_t *mixer = fmixer->mixer; + _old_mixer_info info; + + memset(&info, 0, sizeof(info)); + strlcpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id)); + strlcpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name)); + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_mixer_oss_caps(snd_mixer_oss_file_t *fmixer) +{ + snd_mixer_oss_t *mixer = fmixer->mixer; + int result = 0; + + if (mixer == NULL) + return -EIO; + if (mixer->get_recsrc && mixer->put_recsrc) + result |= SOUND_CAP_EXCL_INPUT; + return result; +} + +static int snd_mixer_oss_devmask(snd_mixer_oss_file_t *fmixer) +{ + snd_mixer_oss_t *mixer = fmixer->mixer; + snd_mixer_oss_slot_t *pslot; + int result = 0, chn; + + if (mixer == NULL) + return -EIO; + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_volume || pslot->put_recsrc) + result |= 1 << chn; + } + return result; +} + +static int snd_mixer_oss_stereodevs(snd_mixer_oss_file_t *fmixer) +{ + snd_mixer_oss_t *mixer = fmixer->mixer; + snd_mixer_oss_slot_t *pslot; + int result = 0, chn; + + if (mixer == NULL) + return -EIO; + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_volume && pslot->stereo) + result |= 1 << chn; + } + return result; +} + +static int snd_mixer_oss_recmask(snd_mixer_oss_file_t *fmixer) +{ + snd_mixer_oss_t *mixer = fmixer->mixer; + int result = 0; + + if (mixer == NULL) + return -EIO; + if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */ + result = mixer->mask_recsrc; + } else { + snd_mixer_oss_slot_t *pslot; + int chn; + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_recsrc) + result |= 1 << chn; + } + } + return result; +} + +static int snd_mixer_oss_get_recsrc(snd_mixer_oss_file_t *fmixer) +{ + snd_mixer_oss_t *mixer = fmixer->mixer; + int result = 0; + + if (mixer == NULL) + return -EIO; + if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */ + int err; + if ((err = mixer->get_recsrc(fmixer, &result)) < 0) + return err; + result = 1 << result; + } else { + snd_mixer_oss_slot_t *pslot; + int chn; + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->get_recsrc) { + int active = 0; + pslot->get_recsrc(fmixer, pslot, &active); + if (active) + result |= 1 << chn; + } + } + } + return mixer->oss_recsrc = result; +} + +static int snd_mixer_oss_set_recsrc(snd_mixer_oss_file_t *fmixer, int recsrc) +{ + snd_mixer_oss_t *mixer = fmixer->mixer; + snd_mixer_oss_slot_t *pslot; + int chn, active; + int result = 0; + + if (mixer == NULL) + return -EIO; + if (mixer->get_recsrc && mixer->put_recsrc) { /* exclusive input */ + if (recsrc & ~mixer->oss_recsrc) + recsrc &= ~mixer->oss_recsrc; + mixer->put_recsrc(fmixer, ffz(~recsrc)); + mixer->get_recsrc(fmixer, &result); + result = 1 << result; + } + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_recsrc) { + active = (recsrc & (1 << chn)) ? 1 : 0; + pslot->put_recsrc(fmixer, pslot, active); + } + } + if (! result) { + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->get_recsrc) { + active = 0; + pslot->get_recsrc(fmixer, pslot, &active); + if (active) + result |= 1 << chn; + } + } + } + return result; +} + +static int snd_mixer_oss_get_volume(snd_mixer_oss_file_t *fmixer, int slot) +{ + snd_mixer_oss_t *mixer = fmixer->mixer; + snd_mixer_oss_slot_t *pslot; + int result = 0, left, right; + + if (mixer == NULL || slot > 30) + return -EIO; + pslot = &mixer->slots[slot]; + left = pslot->volume[0]; + right = pslot->volume[1]; + if (pslot->get_volume) + result = pslot->get_volume(fmixer, pslot, &left, &right); + if (!pslot->stereo) + right = left; + snd_assert(left >= 0 && left <= 100, return -EIO); + snd_assert(right >= 0 && right <= 100, return -EIO); + if (result >= 0) { + pslot->volume[0] = left; + pslot->volume[1] = right; + result = (left & 0xff) | ((right & 0xff) << 8); + } + return result; +} + +static int snd_mixer_oss_set_volume(snd_mixer_oss_file_t *fmixer, + int slot, int volume) +{ + snd_mixer_oss_t *mixer = fmixer->mixer; + snd_mixer_oss_slot_t *pslot; + int result = 0, left = volume & 0xff, right = (volume >> 8) & 0xff; + + if (mixer == NULL || slot > 30) + return -EIO; + pslot = &mixer->slots[slot]; + if (left > 100) + left = 100; + if (right > 100) + right = 100; + if (!pslot->stereo) + right = left; + if (pslot->put_volume) + result = pslot->put_volume(fmixer, pslot, left, right); + if (result < 0) + return result; + pslot->volume[0] = left; + pslot->volume[1] = right; + return (left & 0xff) | ((right & 0xff) << 8); +} + +static int snd_mixer_oss_ioctl1(snd_mixer_oss_file_t *fmixer, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int tmp; + + snd_assert(fmixer != NULL, return -ENXIO); + if (((cmd >> 8) & 0xff) == 'M') { + switch (cmd) { + case SOUND_MIXER_INFO: + return snd_mixer_oss_info(fmixer, argp); + case SOUND_OLD_MIXER_INFO: + return snd_mixer_oss_info_obsolete(fmixer, argp); + case SOUND_MIXER_WRITE_RECSRC: + if (get_user(tmp, p)) + return -EFAULT; + tmp = snd_mixer_oss_set_recsrc(fmixer, tmp); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case OSS_GETVERSION: + return put_user(SNDRV_OSS_VERSION, p); + case OSS_ALSAEMULVER: + return put_user(1, p); + case SOUND_MIXER_READ_DEVMASK: + tmp = snd_mixer_oss_devmask(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_STEREODEVS: + tmp = snd_mixer_oss_stereodevs(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_RECMASK: + tmp = snd_mixer_oss_recmask(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_CAPS: + tmp = snd_mixer_oss_caps(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_RECSRC: + tmp = snd_mixer_oss_get_recsrc(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + } + } + if (cmd & SIOC_IN) { + if (get_user(tmp, p)) + return -EFAULT; + tmp = snd_mixer_oss_set_volume(fmixer, cmd & 0xff, tmp); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + } else if (cmd & SIOC_OUT) { + tmp = snd_mixer_oss_get_volume(fmixer, cmd & 0xff); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + } + return -ENXIO; +} + +static long snd_mixer_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return snd_mixer_oss_ioctl1((snd_mixer_oss_file_t *) file->private_data, cmd, arg); +} + +int snd_mixer_oss_ioctl_card(snd_card_t *card, unsigned int cmd, unsigned long arg) +{ + snd_mixer_oss_file_t fmixer; + + snd_assert(card != NULL, return -ENXIO); + if (card->mixer_oss == NULL) + return -ENXIO; + memset(&fmixer, 0, sizeof(fmixer)); + fmixer.card = card; + fmixer.mixer = card->mixer_oss; + return snd_mixer_oss_ioctl1(&fmixer, cmd, arg); +} + +#ifdef CONFIG_COMPAT +/* all compatible */ +#define snd_mixer_oss_ioctl_compat snd_mixer_oss_ioctl +#else +#define snd_mixer_oss_ioctl_compat NULL +#endif + +/* + * REGISTRATION PART + */ + +static struct file_operations snd_mixer_oss_f_ops = +{ + .owner = THIS_MODULE, + .open = snd_mixer_oss_open, + .release = snd_mixer_oss_release, + .unlocked_ioctl = snd_mixer_oss_ioctl, + .compat_ioctl = snd_mixer_oss_ioctl_compat, +}; + +static snd_minor_t snd_mixer_oss_reg = +{ + .comment = "mixer", + .f_ops = &snd_mixer_oss_f_ops, +}; + +/* + * utilities + */ + +static long snd_mixer_oss_conv(long val, long omin, long omax, long nmin, long nmax) +{ + long orange = omax - omin, nrange = nmax - nmin; + + if (orange == 0) + return 0; + return ((nrange * (val - omin)) + (orange / 2)) / orange + nmin; +} + +/* convert from alsa native to oss values (0-100) */ +static long snd_mixer_oss_conv1(long val, long min, long max, int *old) +{ + if (val == snd_mixer_oss_conv(*old, 0, 100, min, max)) + return *old; + return snd_mixer_oss_conv(val, min, max, 0, 100); +} + +/* convert from oss to alsa native values */ +static long snd_mixer_oss_conv2(long val, long min, long max) +{ + return snd_mixer_oss_conv(val, 0, 100, min, max); +} + +#if 0 +static void snd_mixer_oss_recsrce_set(snd_card_t *card, int slot) +{ + snd_mixer_oss_t *mixer = card->mixer_oss; + if (mixer) + mixer->mask_recsrc |= 1 << slot; +} + +static int snd_mixer_oss_recsrce_get(snd_card_t *card, int slot) +{ + snd_mixer_oss_t *mixer = card->mixer_oss; + if (mixer && (mixer->mask_recsrc & (1 << slot))) + return 1; + return 0; +} +#endif + +#define SNDRV_MIXER_OSS_SIGNATURE 0x65999250 + +#define SNDRV_MIXER_OSS_ITEM_GLOBAL 0 +#define SNDRV_MIXER_OSS_ITEM_GSWITCH 1 +#define SNDRV_MIXER_OSS_ITEM_GROUTE 2 +#define SNDRV_MIXER_OSS_ITEM_GVOLUME 3 +#define SNDRV_MIXER_OSS_ITEM_PSWITCH 4 +#define SNDRV_MIXER_OSS_ITEM_PROUTE 5 +#define SNDRV_MIXER_OSS_ITEM_PVOLUME 6 +#define SNDRV_MIXER_OSS_ITEM_CSWITCH 7 +#define SNDRV_MIXER_OSS_ITEM_CROUTE 8 +#define SNDRV_MIXER_OSS_ITEM_CVOLUME 9 +#define SNDRV_MIXER_OSS_ITEM_CAPTURE 10 + +#define SNDRV_MIXER_OSS_ITEM_COUNT 11 + +#define SNDRV_MIXER_OSS_PRESENT_GLOBAL (1<<0) +#define SNDRV_MIXER_OSS_PRESENT_GSWITCH (1<<1) +#define SNDRV_MIXER_OSS_PRESENT_GROUTE (1<<2) +#define SNDRV_MIXER_OSS_PRESENT_GVOLUME (1<<3) +#define SNDRV_MIXER_OSS_PRESENT_PSWITCH (1<<4) +#define SNDRV_MIXER_OSS_PRESENT_PROUTE (1<<5) +#define SNDRV_MIXER_OSS_PRESENT_PVOLUME (1<<6) +#define SNDRV_MIXER_OSS_PRESENT_CSWITCH (1<<7) +#define SNDRV_MIXER_OSS_PRESENT_CROUTE (1<<8) +#define SNDRV_MIXER_OSS_PRESENT_CVOLUME (1<<9) +#define SNDRV_MIXER_OSS_PRESENT_CAPTURE (1<<10) + +struct slot { + unsigned int signature; + unsigned int present; + unsigned int channels; + unsigned int numid[SNDRV_MIXER_OSS_ITEM_COUNT]; + unsigned int capture_item; + struct snd_mixer_oss_assign_table *assigned; + unsigned int allocated: 1; +}; + +#define ID_UNKNOWN ((unsigned int)-1) + +static snd_kcontrol_t *snd_mixer_oss_test_id(snd_mixer_oss_t *mixer, const char *name, int index) +{ + snd_card_t * card = mixer->card; + snd_ctl_elem_id_t id; + + memset(&id, 0, sizeof(id)); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strcpy(id.name, name); + id.index = index; + return snd_ctl_find_id(card, &id); +} + +static void snd_mixer_oss_get_volume1_vol(snd_mixer_oss_file_t *fmixer, + snd_mixer_oss_slot_t *pslot, + unsigned int numid, + int *left, int *right) +{ + snd_ctl_elem_info_t *uinfo; + snd_ctl_elem_value_t *uctl; + snd_kcontrol_t *kctl; + snd_card_t *card = fmixer->card; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) { + up_read(&card->controls_rwsem); + return; + } + uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL); + uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc); + snd_runtime_check(!kctl->get(kctl, uctl), goto __unalloc); + snd_runtime_check(uinfo->type != SNDRV_CTL_ELEM_TYPE_BOOLEAN || uinfo->value.integer.min != 0 || uinfo->value.integer.max != 1, goto __unalloc); + *left = snd_mixer_oss_conv1(uctl->value.integer.value[0], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[0]); + if (uinfo->count > 1) + *right = snd_mixer_oss_conv1(uctl->value.integer.value[1], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[1]); + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static void snd_mixer_oss_get_volume1_sw(snd_mixer_oss_file_t *fmixer, + snd_mixer_oss_slot_t *pslot, + unsigned int numid, + int *left, int *right, + int route) +{ + snd_ctl_elem_info_t *uinfo; + snd_ctl_elem_value_t *uctl; + snd_kcontrol_t *kctl; + snd_card_t *card = fmixer->card; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) { + up_read(&card->controls_rwsem); + return; + } + uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL); + uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc); + snd_runtime_check(!kctl->get(kctl, uctl), goto __unalloc); + if (!uctl->value.integer.value[0]) { + *left = 0; + if (uinfo->count == 1) + *right = 0; + } + if (uinfo->count > 1 && !uctl->value.integer.value[route ? 3 : 1]) + *right = 0; + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static int snd_mixer_oss_get_volume1(snd_mixer_oss_file_t *fmixer, + snd_mixer_oss_slot_t *pslot, + int *left, int *right) +{ + struct slot *slot = (struct slot *)pslot->private_data; + + *left = *right = 100; + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) { + snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) { + snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) { + snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right); + } + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); + } + return 0; +} + +static void snd_mixer_oss_put_volume1_vol(snd_mixer_oss_file_t *fmixer, + snd_mixer_oss_slot_t *pslot, + unsigned int numid, + int left, int right) +{ + snd_ctl_elem_info_t *uinfo; + snd_ctl_elem_value_t *uctl; + snd_kcontrol_t *kctl; + snd_card_t *card = fmixer->card; + int res; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) + return; + uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL); + uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc); + snd_runtime_check(uinfo->type != SNDRV_CTL_ELEM_TYPE_BOOLEAN || uinfo->value.integer.min != 0 || uinfo->value.integer.max != 1, goto __unalloc); + uctl->value.integer.value[0] = snd_mixer_oss_conv2(left, uinfo->value.integer.min, uinfo->value.integer.max); + if (uinfo->count > 1) + uctl->value.integer.value[1] = snd_mixer_oss_conv2(right, uinfo->value.integer.min, uinfo->value.integer.max); + snd_runtime_check((res = kctl->put(kctl, uctl)) >= 0, goto __unalloc); + if (res > 0) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static void snd_mixer_oss_put_volume1_sw(snd_mixer_oss_file_t *fmixer, + snd_mixer_oss_slot_t *pslot, + unsigned int numid, + int left, int right, + int route) +{ + snd_ctl_elem_info_t *uinfo; + snd_ctl_elem_value_t *uctl; + snd_kcontrol_t *kctl; + snd_card_t *card = fmixer->card; + int res; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + if ((kctl = snd_ctl_find_numid(card, numid)) == NULL) { + up_read(&fmixer->card->controls_rwsem); + return; + } + uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL); + uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + snd_runtime_check(!kctl->info(kctl, uinfo), goto __unalloc); + if (uinfo->count > 1) { + uctl->value.integer.value[0] = left > 0 ? 1 : 0; + uctl->value.integer.value[route ? 3 : 1] = right > 0 ? 1 : 0; + if (route) { + uctl->value.integer.value[1] = + uctl->value.integer.value[2] = 0; + } + } else { + uctl->value.integer.value[0] = (left > 0 || right > 0) ? 1 : 0; + } + snd_runtime_check((res = kctl->put(kctl, uctl)) >= 0, goto __unalloc); + if (res > 0) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static int snd_mixer_oss_put_volume1(snd_mixer_oss_file_t *fmixer, + snd_mixer_oss_slot_t *pslot, + int left, int right) +{ + struct slot *slot = (struct slot *)pslot->private_data; + + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_CVOLUME) + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right); + } + if (left || right) { + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); + } else { + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); + } + } + return 0; +} + +static int snd_mixer_oss_get_recsrc1_sw(snd_mixer_oss_file_t *fmixer, + snd_mixer_oss_slot_t *pslot, + int *active) +{ + struct slot *slot = (struct slot *)pslot->private_data; + int left, right; + + left = right = 1; + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], &left, &right, 0); + *active = (left || right) ? 1 : 0; + return 0; +} + +static int snd_mixer_oss_get_recsrc1_route(snd_mixer_oss_file_t *fmixer, + snd_mixer_oss_slot_t *pslot, + int *active) +{ + struct slot *slot = (struct slot *)pslot->private_data; + int left, right; + + left = right = 1; + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], &left, &right, 1); + *active = (left || right) ? 1 : 0; + return 0; +} + +static int snd_mixer_oss_put_recsrc1_sw(snd_mixer_oss_file_t *fmixer, + snd_mixer_oss_slot_t *pslot, + int active) +{ + struct slot *slot = (struct slot *)pslot->private_data; + + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], active, active, 0); + return 0; +} + +static int snd_mixer_oss_put_recsrc1_route(snd_mixer_oss_file_t *fmixer, + snd_mixer_oss_slot_t *pslot, + int active) +{ + struct slot *slot = (struct slot *)pslot->private_data; + + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], active, active, 1); + return 0; +} + +static int snd_mixer_oss_get_recsrc2(snd_mixer_oss_file_t *fmixer, unsigned int *active_index) +{ + snd_card_t *card = fmixer->card; + snd_mixer_oss_t *mixer = fmixer->mixer; + snd_kcontrol_t *kctl; + snd_mixer_oss_slot_t *pslot; + struct slot *slot; + snd_ctl_elem_info_t *uinfo; + snd_ctl_elem_value_t *uctl; + int err, idx; + + uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL); + uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) { + err = -ENOMEM; + goto __unlock; + } + down_read(&card->controls_rwsem); + kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0); + snd_runtime_check(kctl != NULL, err = -ENOENT; goto __unlock); + snd_runtime_check(!(err = kctl->info(kctl, uinfo)), goto __unlock); + snd_runtime_check(!(err = kctl->get(kctl, uctl)), goto __unlock); + for (idx = 0; idx < 32; idx++) { + if (!(mixer->mask_recsrc & (1 << idx))) + continue; + pslot = &mixer->slots[idx]; + slot = (struct slot *)pslot->private_data; + if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE) + continue; + if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE)) + continue; + if (slot->capture_item == uctl->value.enumerated.item[0]) { + *active_index = idx; + break; + } + } + err = 0; + __unlock: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); + return err; +} + +static int snd_mixer_oss_put_recsrc2(snd_mixer_oss_file_t *fmixer, unsigned int active_index) +{ + snd_card_t *card = fmixer->card; + snd_mixer_oss_t *mixer = fmixer->mixer; + snd_kcontrol_t *kctl; + snd_mixer_oss_slot_t *pslot; + struct slot *slot = NULL; + snd_ctl_elem_info_t *uinfo; + snd_ctl_elem_value_t *uctl; + int err; + unsigned int idx; + + uinfo = kcalloc(1, sizeof(*uinfo), GFP_KERNEL); + uctl = kcalloc(1, sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) { + err = -ENOMEM; + goto __unlock; + } + down_read(&card->controls_rwsem); + kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0); + snd_runtime_check(kctl != NULL, err = -ENOENT; goto __unlock); + snd_runtime_check(!(err = kctl->info(kctl, uinfo)), goto __unlock); + for (idx = 0; idx < 32; idx++) { + if (!(mixer->mask_recsrc & (1 << idx))) + continue; + pslot = &mixer->slots[idx]; + slot = (struct slot *)pslot->private_data; + if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE) + continue; + if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE)) + continue; + if (idx == active_index) + break; + slot = NULL; + } + snd_runtime_check(slot != NULL, goto __unlock); + for (idx = 0; idx < uinfo->count; idx++) + uctl->value.enumerated.item[idx] = slot->capture_item; + snd_runtime_check((err = kctl->put(kctl, uctl)) >= 0, ); + if (err > 0) + snd_ctl_notify(fmixer->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + err = 0; + __unlock: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); + return err; +} + +struct snd_mixer_oss_assign_table { + int oss_id; + const char *name; + int index; +}; + +static int snd_mixer_oss_build_test(snd_mixer_oss_t *mixer, struct slot *slot, const char *name, int index, int item) +{ + snd_ctl_elem_info_t *info; + snd_kcontrol_t *kcontrol; + snd_card_t *card = mixer->card; + int err; + + down_read(&card->controls_rwsem); + kcontrol = snd_mixer_oss_test_id(mixer, name, index); + if (kcontrol == NULL) { + up_read(&card->controls_rwsem); + return 0; + } + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) { + up_read(&card->controls_rwsem); + return -ENOMEM; + } + if ((err = kcontrol->info(kcontrol, info)) < 0) { + up_read(&card->controls_rwsem); + kfree(info); + return err; + } + slot->numid[item] = kcontrol->id.numid; + up_read(&card->controls_rwsem); + if (info->count > slot->channels) + slot->channels = info->count; + slot->present |= 1 << item; + kfree(info); + return 0; +} + +static void snd_mixer_oss_slot_free(snd_mixer_oss_slot_t *chn) +{ + struct slot *p = (struct slot *)chn->private_data; + if (p) { + if (p->allocated && p->assigned) { + kfree(p->assigned->name); + kfree(p->assigned); + } + kfree(p); + } +} + +static void mixer_slot_clear(snd_mixer_oss_slot_t *rslot) +{ + int idx = rslot->number; /* remember this */ + if (rslot->private_free) + rslot->private_free(rslot); + memset(rslot, 0, sizeof(*rslot)); + rslot->number = idx; +} + +/* + * build an OSS mixer element. + * ptr_allocated means the entry is dynamically allocated (change via proc file). + * when replace_old = 1, the old entry is replaced with the new one. + */ +static int snd_mixer_oss_build_input(snd_mixer_oss_t *mixer, struct snd_mixer_oss_assign_table *ptr, int ptr_allocated, int replace_old) +{ + struct slot slot; + struct slot *pslot; + snd_kcontrol_t *kctl; + snd_mixer_oss_slot_t *rslot; + char str[64]; + + /* check if already assigned */ + if (mixer->slots[ptr->oss_id].get_volume && ! replace_old) + return 0; + + memset(&slot, 0, sizeof(slot)); + memset(slot.numid, 0xff, sizeof(slot.numid)); /* ID_UNKNOWN */ + if (snd_mixer_oss_build_test(mixer, &slot, ptr->name, ptr->index, + SNDRV_MIXER_OSS_ITEM_GLOBAL)) + return 0; + sprintf(str, "%s Switch", ptr->name); + if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_GSWITCH)) + return 0; + sprintf(str, "%s Route", ptr->name); + if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_GROUTE)) + return 0; + sprintf(str, "%s Volume", ptr->name); + if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_GVOLUME)) + return 0; + sprintf(str, "%s Playback Switch", ptr->name); + if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_PSWITCH)) + return 0; + sprintf(str, "%s Playback Route", ptr->name); + if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_PROUTE)) + return 0; + sprintf(str, "%s Playback Volume", ptr->name); + if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_PVOLUME)) + return 0; + sprintf(str, "%s Capture Switch", ptr->name); + if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_CSWITCH)) + return 0; + sprintf(str, "%s Capture Route", ptr->name); + if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_CROUTE)) + return 0; + sprintf(str, "%s Capture Volume", ptr->name); + if (snd_mixer_oss_build_test(mixer, &slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_CVOLUME)) + return 0; + down_read(&mixer->card->controls_rwsem); + if (ptr->index == 0 && (kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0)) != NULL) { + snd_ctl_elem_info_t *uinfo; + + uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL); + if (! uinfo) { + up_read(&mixer->card->controls_rwsem); + return -ENOMEM; + } + + memset(uinfo, 0, sizeof(*uinfo)); + if (kctl->info(kctl, uinfo)) { + up_read(&mixer->card->controls_rwsem); + return 0; + } + strcpy(str, ptr->name); + if (!strcmp(str, "Master")) + strcpy(str, "Mix"); + if (!strcmp(str, "Master Mono")) + strcpy(str, "Mix Mono"); + slot.capture_item = 0; + if (!strcmp(uinfo->value.enumerated.name, str)) { + slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE; + } else { + for (slot.capture_item = 1; slot.capture_item < uinfo->value.enumerated.items; slot.capture_item++) { + uinfo->value.enumerated.item = slot.capture_item; + if (kctl->info(kctl, uinfo)) { + up_read(&mixer->card->controls_rwsem); + return 0; + } + if (!strcmp(uinfo->value.enumerated.name, str)) { + slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE; + break; + } + } + } + kfree(uinfo); + } + up_read(&mixer->card->controls_rwsem); + if (slot.present != 0) { + pslot = (struct slot *)kmalloc(sizeof(slot), GFP_KERNEL); + snd_runtime_check(pslot != NULL, return -ENOMEM); + *pslot = slot; + pslot->signature = SNDRV_MIXER_OSS_SIGNATURE; + pslot->assigned = ptr; + pslot->allocated = ptr_allocated; + rslot = &mixer->slots[ptr->oss_id]; + mixer_slot_clear(rslot); + rslot->stereo = slot.channels > 1 ? 1 : 0; + rslot->get_volume = snd_mixer_oss_get_volume1; + rslot->put_volume = snd_mixer_oss_put_volume1; + /* note: ES18xx have both Capture Source and XX Capture Volume !!! */ + if (slot.present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) { + rslot->get_recsrc = snd_mixer_oss_get_recsrc1_sw; + rslot->put_recsrc = snd_mixer_oss_put_recsrc1_sw; + } else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CROUTE) { + rslot->get_recsrc = snd_mixer_oss_get_recsrc1_route; + rslot->put_recsrc = snd_mixer_oss_put_recsrc1_route; + } else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CAPTURE) { + mixer->mask_recsrc |= 1 << ptr->oss_id; + } + rslot->private_data = pslot; + rslot->private_free = snd_mixer_oss_slot_free; + return 1; + } + return 0; +} + +/* + */ +#define MIXER_VOL(name) [SOUND_MIXER_##name] = #name +static char *oss_mixer_names[SNDRV_OSS_MAX_MIXERS] = { + MIXER_VOL(VOLUME), + MIXER_VOL(BASS), + MIXER_VOL(TREBLE), + MIXER_VOL(SYNTH), + MIXER_VOL(PCM), + MIXER_VOL(SPEAKER), + MIXER_VOL(LINE), + MIXER_VOL(MIC), + MIXER_VOL(CD), + MIXER_VOL(IMIX), + MIXER_VOL(ALTPCM), + MIXER_VOL(RECLEV), + MIXER_VOL(IGAIN), + MIXER_VOL(OGAIN), + MIXER_VOL(LINE1), + MIXER_VOL(LINE2), + MIXER_VOL(LINE3), + MIXER_VOL(DIGITAL1), + MIXER_VOL(DIGITAL2), + MIXER_VOL(DIGITAL3), + MIXER_VOL(PHONEIN), + MIXER_VOL(PHONEOUT), + MIXER_VOL(VIDEO), + MIXER_VOL(RADIO), + MIXER_VOL(MONITOR), +}; + +/* + * /proc interface + */ + +static void snd_mixer_oss_proc_read(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + snd_mixer_oss_t *mixer = entry->private_data; + int i; + + down(&mixer->reg_mutex); + for (i = 0; i < SNDRV_OSS_MAX_MIXERS; i++) { + struct slot *p; + + if (! oss_mixer_names[i]) + continue; + p = (struct slot *)mixer->slots[i].private_data; + snd_iprintf(buffer, "%s ", oss_mixer_names[i]); + if (p && p->assigned) + snd_iprintf(buffer, "\"%s\" %d\n", + p->assigned->name, + p->assigned->index); + else + snd_iprintf(buffer, "\"\" 0\n"); + } + up(&mixer->reg_mutex); +} + +static void snd_mixer_oss_proc_write(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + snd_mixer_oss_t *mixer = entry->private_data; + char line[128], str[32], idxstr[16], *cptr; + int ch, idx; + struct snd_mixer_oss_assign_table *tbl; + struct slot *slot; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + cptr = snd_info_get_str(str, line, sizeof(str)); + for (ch = 0; ch < SNDRV_OSS_MAX_MIXERS; ch++) + if (oss_mixer_names[ch] && strcmp(oss_mixer_names[ch], str) == 0) + break; + if (ch >= SNDRV_OSS_MAX_MIXERS) { + snd_printk(KERN_ERR "mixer_oss: invalid OSS volume '%s'\n", str); + continue; + } + cptr = snd_info_get_str(str, cptr, sizeof(str)); + if (! *str) { + /* remove the entry */ + down(&mixer->reg_mutex); + mixer_slot_clear(&mixer->slots[ch]); + up(&mixer->reg_mutex); + continue; + } + snd_info_get_str(idxstr, cptr, sizeof(idxstr)); + idx = simple_strtoul(idxstr, NULL, 10); + if (idx >= 0x4000) { /* too big */ + snd_printk(KERN_ERR "mixer_oss: invalid index %d\n", idx); + continue; + } + down(&mixer->reg_mutex); + slot = (struct slot *)mixer->slots[ch].private_data; + if (slot && slot->assigned && + slot->assigned->index == idx && ! strcmp(slot->assigned->name, str)) + /* not changed */ + goto __unlock; + tbl = kmalloc(sizeof(*tbl), GFP_KERNEL); + if (! tbl) { + snd_printk(KERN_ERR "mixer_oss: no memory\n"); + goto __unlock; + } + tbl->oss_id = ch; + tbl->name = snd_kmalloc_strdup(str, GFP_KERNEL); + if (! tbl->name) { + kfree(tbl); + goto __unlock; + } + tbl->index = idx; + if (snd_mixer_oss_build_input(mixer, tbl, 1, 1) <= 0) { + kfree(tbl->name); + kfree(tbl); + } + __unlock: + up(&mixer->reg_mutex); + } +} + +static void snd_mixer_oss_proc_init(snd_mixer_oss_t *mixer) +{ + snd_info_entry_t *entry; + + entry = snd_info_create_card_entry(mixer->card, "oss_mixer", + mixer->card->proc_root); + if (! entry) + return; + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->c.text.read_size = 8192; + entry->c.text.read = snd_mixer_oss_proc_read; + entry->c.text.write_size = 8192; + entry->c.text.write = snd_mixer_oss_proc_write; + entry->private_data = mixer; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + mixer->proc_entry = entry; +} + +static void snd_mixer_oss_proc_done(snd_mixer_oss_t *mixer) +{ + if (mixer->proc_entry) { + snd_info_unregister(mixer->proc_entry); + mixer->proc_entry = NULL; + } +} + +static void snd_mixer_oss_build(snd_mixer_oss_t *mixer) +{ + static struct snd_mixer_oss_assign_table table[] = { + { SOUND_MIXER_VOLUME, "Master", 0 }, + { SOUND_MIXER_VOLUME, "Front", 0 }, /* fallback */ + { SOUND_MIXER_BASS, "Tone Control - Bass", 0 }, + { SOUND_MIXER_TREBLE, "Tone Control - Treble", 0 }, + { SOUND_MIXER_SYNTH, "Synth", 0 }, + { SOUND_MIXER_SYNTH, "FM", 0 }, /* fallback */ + { SOUND_MIXER_SYNTH, "Music", 0 }, /* fallback */ + { SOUND_MIXER_PCM, "PCM", 0 }, + { SOUND_MIXER_SPEAKER, "PC Speaker", 0 }, + { SOUND_MIXER_LINE, "Line", 0 }, + { SOUND_MIXER_MIC, "Mic", 0 }, + { SOUND_MIXER_CD, "CD", 0 }, + { SOUND_MIXER_IMIX, "Monitor Mix", 0 }, + { SOUND_MIXER_ALTPCM, "PCM", 1 }, + { SOUND_MIXER_ALTPCM, "Headphone", 0 }, /* fallback */ + { SOUND_MIXER_ALTPCM, "Wave", 0 }, /* fallback */ + { SOUND_MIXER_RECLEV, "-- nothing --", 0 }, + { SOUND_MIXER_IGAIN, "Capture", 0 }, + { SOUND_MIXER_OGAIN, "Playback", 0 }, + { SOUND_MIXER_LINE1, "Aux", 0 }, + { SOUND_MIXER_LINE2, "Aux", 1 }, + { SOUND_MIXER_LINE3, "Aux", 2 }, + { SOUND_MIXER_DIGITAL1, "Digital", 0 }, + { SOUND_MIXER_DIGITAL1, "IEC958", 0 }, /* fallback */ + { SOUND_MIXER_DIGITAL1, "IEC958 Optical", 0 }, /* fallback */ + { SOUND_MIXER_DIGITAL1, "IEC958 Coaxial", 0 }, /* fallback */ + { SOUND_MIXER_DIGITAL2, "Digital", 1 }, + { SOUND_MIXER_DIGITAL3, "Digital", 2 }, + { SOUND_MIXER_PHONEIN, "Phone", 0 }, + { SOUND_MIXER_PHONEOUT, "Master Mono", 0 }, + { SOUND_MIXER_PHONEOUT, "Phone", 0 }, /* fallback */ + { SOUND_MIXER_VIDEO, "Video", 0 }, + { SOUND_MIXER_RADIO, "Radio", 0 }, + { SOUND_MIXER_MONITOR, "Monitor", 0 } + }; + unsigned int idx; + + for (idx = 0; idx < ARRAY_SIZE(table); idx++) + snd_mixer_oss_build_input(mixer, &table[idx], 0, 0); + if (mixer->mask_recsrc) { + mixer->get_recsrc = snd_mixer_oss_get_recsrc2; + mixer->put_recsrc = snd_mixer_oss_put_recsrc2; + } +} + +/* + * + */ + +static int snd_mixer_oss_free1(void *private) +{ + snd_mixer_oss_t *mixer = private; + snd_card_t * card; + int idx; + + snd_assert(mixer != NULL, return -ENXIO); + card = mixer->card; + snd_assert(mixer == card->mixer_oss, return -ENXIO); + card->mixer_oss = NULL; + for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) { + snd_mixer_oss_slot_t *chn = &mixer->slots[idx]; + if (chn->private_free) + chn->private_free(chn); + } + kfree(mixer); + return 0; +} + +static int snd_mixer_oss_notify_handler(snd_card_t * card, int cmd) +{ + snd_mixer_oss_t *mixer; + + if (cmd == SND_MIXER_OSS_NOTIFY_REGISTER) { + char name[128]; + int idx, err; + + mixer = kcalloc(2, sizeof(*mixer), GFP_KERNEL); + if (mixer == NULL) + return -ENOMEM; + init_MUTEX(&mixer->reg_mutex); + sprintf(name, "mixer%i%i", card->number, 0); + if ((err = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, + card, 0, + &snd_mixer_oss_reg, + name)) < 0) { + snd_printk("unable to register OSS mixer device %i:%i\n", card->number, 0); + kfree(mixer); + return err; + } + mixer->oss_dev_alloc = 1; + mixer->card = card; + if (*card->mixername) + strlcpy(mixer->name, card->mixername, sizeof(mixer->name)); + else + strlcpy(mixer->name, name, sizeof(mixer->name)); +#ifdef SNDRV_OSS_INFO_DEV_MIXERS + snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIXERS, + card->number, + mixer->name); +#endif + for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) + mixer->slots[idx].number = idx; + card->mixer_oss = mixer; + snd_mixer_oss_build(mixer); + snd_mixer_oss_proc_init(mixer); + } else if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT) { + mixer = card->mixer_oss; + if (mixer == NULL || !mixer->oss_dev_alloc) + return 0; + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, mixer->card, 0); + mixer->oss_dev_alloc = 0; + } else { /* free */ + mixer = card->mixer_oss; + if (mixer == NULL) + return 0; +#ifdef SNDRV_OSS_INFO_DEV_MIXERS + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIXERS, mixer->card->number); +#endif + if (mixer->oss_dev_alloc) + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, mixer->card, 0); + snd_mixer_oss_proc_done(mixer); + return snd_mixer_oss_free1(mixer); + } + return 0; +} + +static int __init alsa_mixer_oss_init(void) +{ + int idx; + + snd_mixer_oss_notify_callback = snd_mixer_oss_notify_handler; + for (idx = 0; idx < SNDRV_CARDS; idx++) { + if (snd_cards[idx]) + snd_mixer_oss_notify_handler(snd_cards[idx], SND_MIXER_OSS_NOTIFY_REGISTER); + } + return 0; +} + +static void __exit alsa_mixer_oss_exit(void) +{ + int idx; + + snd_mixer_oss_notify_callback = NULL; + for (idx = 0; idx < SNDRV_CARDS; idx++) { + if (snd_cards[idx]) + snd_mixer_oss_notify_handler(snd_cards[idx], SND_MIXER_OSS_NOTIFY_FREE); + } +} + +module_init(alsa_mixer_oss_init) +module_exit(alsa_mixer_oss_exit) + +EXPORT_SYMBOL(snd_mixer_oss_ioctl_card); diff --git a/sound/core/oss/mulaw.c b/sound/core/oss/mulaw.c new file mode 100644 index 000000000000..44ec4c66eb19 --- /dev/null +++ b/sound/core/oss/mulaw.c @@ -0,0 +1,308 @@ +/* + * Mu-Law conversion Plug-In Interface + * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> + * Uros Bizjak <uros@kss-loka.si> + * + * Based on reference implementation by Sun Microsystems, Inc. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "pcm_plugin.h" + +#define SIGN_BIT (0x80) /* Sign bit for a u-law byte. */ +#define QUANT_MASK (0xf) /* Quantization field mask. */ +#define NSEGS (8) /* Number of u-law segments. */ +#define SEG_SHIFT (4) /* Left shift for segment number. */ +#define SEG_MASK (0x70) /* Segment field mask. */ + +static inline int val_seg(int val) +{ + int r = 0; + val >>= 7; + if (val & 0xf0) { + val >>= 4; + r += 4; + } + if (val & 0x0c) { + val >>= 2; + r += 2; + } + if (val & 0x02) + r += 1; + return r; +} + +#define BIAS (0x84) /* Bias for linear code. */ + +/* + * linear2ulaw() - Convert a linear PCM value to u-law + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +static unsigned char linear2ulaw(int pcm_val) /* 2's complement (16-bit range) */ +{ + int mask; + int seg; + unsigned char uval; + + /* Get the sign and the magnitude of the value. */ + if (pcm_val < 0) { + pcm_val = BIAS - pcm_val; + mask = 0x7F; + } else { + pcm_val += BIAS; + mask = 0xFF; + } + if (pcm_val > 0x7FFF) + pcm_val = 0x7FFF; + + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF); + return uval ^ mask; +} + +/* + * ulaw2linear() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +static int ulaw2linear(unsigned char u_val) +{ + int t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT; + + return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} + +/* + * Basic Mu-Law plugin + */ + +typedef void (*mulaw_f)(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames); + +typedef struct mulaw_private_data { + mulaw_f func; + int conv; +} mulaw_t; + +static void mulaw_decode(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames) +{ +#define PUT_S16_LABELS +#include "plugin_ops.h" +#undef PUT_S16_LABELS + mulaw_t *data = (mulaw_t *)plugin->extra_data; + void *put = put_s16_labels[data->conv]; + int channel; + int nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; ++channel) { + char *src; + char *dst; + int src_step, dst_step; + snd_pcm_uframes_t frames1; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + frames1 = frames; + while (frames1-- > 0) { + signed short sample = ulaw2linear(*src); + goto *put; +#define PUT_S16_END after +#include "plugin_ops.h" +#undef PUT_S16_END + after: + src += src_step; + dst += dst_step; + } + } +} + +static void mulaw_encode(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames) +{ +#define GET_S16_LABELS +#include "plugin_ops.h" +#undef GET_S16_LABELS + mulaw_t *data = (mulaw_t *)plugin->extra_data; + void *get = get_s16_labels[data->conv]; + int channel; + int nchannels = plugin->src_format.channels; + signed short sample = 0; + for (channel = 0; channel < nchannels; ++channel) { + char *src; + char *dst; + int src_step, dst_step; + snd_pcm_uframes_t frames1; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + frames1 = frames; + while (frames1-- > 0) { + goto *get; +#define GET_S16_END after +#include "plugin_ops.h" +#undef GET_S16_END + after: + *dst = linear2ulaw(sample); + src += src_step; + dst += dst_step; + } + } +} + +static snd_pcm_sframes_t mulaw_transfer(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames) +{ + mulaw_t *data; + + snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO); + if (frames == 0) + return 0; +#ifdef CONFIG_SND_DEBUG + { + unsigned int channel; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + snd_assert(src_channels[channel].area.first % 8 == 0 && + src_channels[channel].area.step % 8 == 0, + return -ENXIO); + snd_assert(dst_channels[channel].area.first % 8 == 0 && + dst_channels[channel].area.step % 8 == 0, + return -ENXIO); + } + } +#endif + data = (mulaw_t *)plugin->extra_data; + data->func(plugin, src_channels, dst_channels, frames); + return frames; +} + +int snd_pcm_plugin_build_mulaw(snd_pcm_plug_t *plug, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + snd_pcm_plugin_t **r_plugin) +{ + int err; + mulaw_t *data; + snd_pcm_plugin_t *plugin; + snd_pcm_plugin_format_t *format; + mulaw_f func; + + snd_assert(r_plugin != NULL, return -ENXIO); + *r_plugin = NULL; + + snd_assert(src_format->rate == dst_format->rate, return -ENXIO); + snd_assert(src_format->channels == dst_format->channels, return -ENXIO); + + if (dst_format->format == SNDRV_PCM_FORMAT_MU_LAW) { + format = src_format; + func = mulaw_encode; + } + else if (src_format->format == SNDRV_PCM_FORMAT_MU_LAW) { + format = dst_format; + func = mulaw_decode; + } + else { + snd_BUG(); + return -EINVAL; + } + snd_assert(snd_pcm_format_linear(format->format) != 0, return -ENXIO); + + err = snd_pcm_plugin_build(plug, "Mu-Law<->linear conversion", + src_format, dst_format, + sizeof(mulaw_t), &plugin); + if (err < 0) + return err; + data = (mulaw_t*)plugin->extra_data; + data->func = func; + data->conv = getput_index(format->format); + snd_assert(data->conv >= 0 && data->conv < 4*2*2, return -EINVAL); + plugin->transfer = mulaw_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c new file mode 100644 index 000000000000..1a805020f57a --- /dev/null +++ b/sound/core/oss/pcm_oss.c @@ -0,0 +1,2530 @@ +/* + * Digital Audio (PCM) abstract layer / OSS compatible + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#if 0 +#define PLUGIN_DEBUG +#endif +#if 0 +#define OSS_DEBUG +#endif + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/vmalloc.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "pcm_plugin.h" +#include <sound/info.h> +#include <linux/soundcard.h> +#include <sound/initval.h> + +#define OSS_ALSAEMULVER _SIOR ('M', 249, int) + +static int dsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 0}; +static int adsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +static int nonblock_open = 1; + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Abramo Bagnara <abramo@alsa-project.org>"); +MODULE_DESCRIPTION("PCM OSS emulation for ALSA."); +MODULE_LICENSE("GPL"); +module_param_array(dsp_map, int, NULL, 0444); +MODULE_PARM_DESC(dsp_map, "PCM device number assigned to 1st OSS device."); +module_param_array(adsp_map, int, NULL, 0444); +MODULE_PARM_DESC(adsp_map, "PCM device number assigned to 2nd OSS device."); +module_param(nonblock_open, bool, 0644); +MODULE_PARM_DESC(nonblock_open, "Don't block opening busy PCM devices."); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM1); + +extern int snd_mixer_oss_ioctl_card(snd_card_t *card, unsigned int cmd, unsigned long arg); +static int snd_pcm_oss_get_rate(snd_pcm_oss_file_t *pcm_oss_file); +static int snd_pcm_oss_get_channels(snd_pcm_oss_file_t *pcm_oss_file); +static int snd_pcm_oss_get_format(snd_pcm_oss_file_t *pcm_oss_file); + +static inline mm_segment_t snd_enter_user(void) +{ + mm_segment_t fs = get_fs(); + set_fs(get_ds()); + return fs; +} + +static inline void snd_leave_user(mm_segment_t fs) +{ + set_fs(fs); +} + +static int snd_pcm_oss_plugin_clear(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_plugin_t *plugin, *next; + + plugin = runtime->oss.plugin_first; + while (plugin) { + next = plugin->next; + snd_pcm_plugin_free(plugin); + plugin = next; + } + runtime->oss.plugin_first = runtime->oss.plugin_last = NULL; + return 0; +} + +static int snd_pcm_plugin_insert(snd_pcm_plugin_t *plugin) +{ + snd_pcm_runtime_t *runtime = plugin->plug->runtime; + plugin->next = runtime->oss.plugin_first; + plugin->prev = NULL; + if (runtime->oss.plugin_first) { + runtime->oss.plugin_first->prev = plugin; + runtime->oss.plugin_first = plugin; + } else { + runtime->oss.plugin_last = + runtime->oss.plugin_first = plugin; + } + return 0; +} + +int snd_pcm_plugin_append(snd_pcm_plugin_t *plugin) +{ + snd_pcm_runtime_t *runtime = plugin->plug->runtime; + plugin->next = NULL; + plugin->prev = runtime->oss.plugin_last; + if (runtime->oss.plugin_last) { + runtime->oss.plugin_last->next = plugin; + runtime->oss.plugin_last = plugin; + } else { + runtime->oss.plugin_last = + runtime->oss.plugin_first = plugin; + } + return 0; +} + +static long snd_pcm_oss_bytes(snd_pcm_substream_t *substream, long frames) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_uframes_t buffer_size = snd_pcm_lib_buffer_bytes(substream); + frames = frames_to_bytes(runtime, frames); + if (buffer_size == runtime->oss.buffer_bytes) + return frames; + return (runtime->oss.buffer_bytes * frames) / buffer_size; +} + +static long snd_pcm_alsa_frames(snd_pcm_substream_t *substream, long bytes) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_uframes_t buffer_size = snd_pcm_lib_buffer_bytes(substream); + if (buffer_size == runtime->oss.buffer_bytes) + return bytes_to_frames(runtime, bytes); + return bytes_to_frames(runtime, (buffer_size * bytes) / runtime->oss.buffer_bytes); +} + +static int snd_pcm_oss_format_from(int format) +{ + switch (format) { + case AFMT_MU_LAW: return SNDRV_PCM_FORMAT_MU_LAW; + case AFMT_A_LAW: return SNDRV_PCM_FORMAT_A_LAW; + case AFMT_IMA_ADPCM: return SNDRV_PCM_FORMAT_IMA_ADPCM; + case AFMT_U8: return SNDRV_PCM_FORMAT_U8; + case AFMT_S16_LE: return SNDRV_PCM_FORMAT_S16_LE; + case AFMT_S16_BE: return SNDRV_PCM_FORMAT_S16_BE; + case AFMT_S8: return SNDRV_PCM_FORMAT_S8; + case AFMT_U16_LE: return SNDRV_PCM_FORMAT_U16_LE; + case AFMT_U16_BE: return SNDRV_PCM_FORMAT_U16_BE; + case AFMT_MPEG: return SNDRV_PCM_FORMAT_MPEG; + default: return SNDRV_PCM_FORMAT_U8; + } +} + +static int snd_pcm_oss_format_to(int format) +{ + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: return AFMT_MU_LAW; + case SNDRV_PCM_FORMAT_A_LAW: return AFMT_A_LAW; + case SNDRV_PCM_FORMAT_IMA_ADPCM: return AFMT_IMA_ADPCM; + case SNDRV_PCM_FORMAT_U8: return AFMT_U8; + case SNDRV_PCM_FORMAT_S16_LE: return AFMT_S16_LE; + case SNDRV_PCM_FORMAT_S16_BE: return AFMT_S16_BE; + case SNDRV_PCM_FORMAT_S8: return AFMT_S8; + case SNDRV_PCM_FORMAT_U16_LE: return AFMT_U16_LE; + case SNDRV_PCM_FORMAT_U16_BE: return AFMT_U16_BE; + case SNDRV_PCM_FORMAT_MPEG: return AFMT_MPEG; + default: return -EINVAL; + } +} + +static int snd_pcm_oss_period_size(snd_pcm_substream_t *substream, + snd_pcm_hw_params_t *oss_params, + snd_pcm_hw_params_t *slave_params) +{ + size_t s; + size_t oss_buffer_size, oss_period_size, oss_periods; + size_t min_period_size, max_period_size; + snd_pcm_runtime_t *runtime = substream->runtime; + size_t oss_frame_size; + + oss_frame_size = snd_pcm_format_physical_width(params_format(oss_params)) * + params_channels(oss_params) / 8; + + oss_buffer_size = snd_pcm_plug_client_size(substream, + snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, NULL)) * oss_frame_size; + oss_buffer_size = 1 << ld2(oss_buffer_size); + if (atomic_read(&runtime->mmap_count)) { + if (oss_buffer_size > runtime->oss.mmap_bytes) + oss_buffer_size = runtime->oss.mmap_bytes; + } + + if (substream->oss.setup && + substream->oss.setup->period_size > 16) + oss_period_size = substream->oss.setup->period_size; + else if (runtime->oss.fragshift) { + oss_period_size = 1 << runtime->oss.fragshift; + if (oss_period_size > oss_buffer_size / 2) + oss_period_size = oss_buffer_size / 2; + } else { + int sd; + size_t bytes_per_sec = params_rate(oss_params) * snd_pcm_format_physical_width(params_format(oss_params)) * params_channels(oss_params) / 8; + + oss_period_size = oss_buffer_size; + do { + oss_period_size /= 2; + } while (oss_period_size > bytes_per_sec); + if (runtime->oss.subdivision == 0) { + sd = 4; + if (oss_period_size / sd > 4096) + sd *= 2; + if (oss_period_size / sd < 4096) + sd = 1; + } else + sd = runtime->oss.subdivision; + oss_period_size /= sd; + if (oss_period_size < 16) + oss_period_size = 16; + } + + min_period_size = snd_pcm_plug_client_size(substream, + snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL)); + min_period_size *= oss_frame_size; + min_period_size = 1 << (ld2(min_period_size - 1) + 1); + if (oss_period_size < min_period_size) + oss_period_size = min_period_size; + + max_period_size = snd_pcm_plug_client_size(substream, + snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL)); + max_period_size *= oss_frame_size; + max_period_size = 1 << ld2(max_period_size); + if (oss_period_size > max_period_size) + oss_period_size = max_period_size; + + oss_periods = oss_buffer_size / oss_period_size; + + if (substream->oss.setup) { + if (substream->oss.setup->periods > 1) + oss_periods = substream->oss.setup->periods; + } + + s = snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL); + if (runtime->oss.maxfrags && s > runtime->oss.maxfrags) + s = runtime->oss.maxfrags; + if (oss_periods > s) + oss_periods = s; + + s = snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL); + if (s < 2) + s = 2; + if (oss_periods < s) + oss_periods = s; + + while (oss_period_size * oss_periods > oss_buffer_size) + oss_period_size /= 2; + + snd_assert(oss_period_size >= 16, return -EINVAL); + runtime->oss.period_bytes = oss_period_size; + runtime->oss.period_frames = 1; + runtime->oss.periods = oss_periods; + return 0; +} + +static int choose_rate(snd_pcm_substream_t *substream, + snd_pcm_hw_params_t *params, unsigned int best_rate) +{ + snd_interval_t *it; + snd_pcm_hw_params_t *save; + unsigned int rate, prev; + + save = kmalloc(sizeof(*save), GFP_KERNEL); + if (save == NULL) + return -ENOMEM; + *save = *params; + it = hw_param_interval(save, SNDRV_PCM_HW_PARAM_RATE); + + /* try multiples of the best rate */ + rate = best_rate; + for (;;) { + if (it->max < rate || (it->max == rate && it->openmax)) + break; + if (it->min < rate || (it->min == rate && !it->openmin)) { + int ret; + ret = snd_pcm_hw_param_set(substream, params, + SNDRV_PCM_HW_PARAM_RATE, + rate, 0); + if (ret == (int)rate) { + kfree(save); + return rate; + } + *params = *save; + } + prev = rate; + rate += best_rate; + if (rate <= prev) + break; + } + + /* not found, use the nearest rate */ + kfree(save); + return snd_pcm_hw_param_near(substream, params, SNDRV_PCM_HW_PARAM_RATE, best_rate, NULL); +} + +static int snd_pcm_oss_change_params(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_hw_params_t *params, *sparams; + snd_pcm_sw_params_t *sw_params; + ssize_t oss_buffer_size, oss_period_size; + size_t oss_frame_size; + int err; + int direct; + int format, sformat, n; + snd_mask_t sformat_mask; + snd_mask_t mask; + + sw_params = kmalloc(sizeof(*sw_params), GFP_KERNEL); + params = kmalloc(sizeof(*params), GFP_KERNEL); + sparams = kmalloc(sizeof(*sparams), GFP_KERNEL); + if (!sw_params || !params || !sparams) { + snd_printd("No memory\n"); + err = -ENOMEM; + goto failure; + } + + if (atomic_read(&runtime->mmap_count)) { + direct = 1; + } else { + snd_pcm_oss_setup_t *setup = substream->oss.setup; + direct = (setup != NULL && setup->direct); + } + + _snd_pcm_hw_params_any(sparams); + _snd_pcm_hw_param_setinteger(sparams, SNDRV_PCM_HW_PARAM_PERIODS); + _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0); + snd_mask_none(&mask); + if (atomic_read(&runtime->mmap_count)) + snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else { + snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED); + if (!direct) + snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + } + err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask); + if (err < 0) { + snd_printd("No usable accesses\n"); + err = -EINVAL; + goto failure; + } + choose_rate(substream, sparams, runtime->oss.rate); + snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_CHANNELS, runtime->oss.channels, NULL); + + format = snd_pcm_oss_format_from(runtime->oss.format); + + sformat_mask = *hw_param_mask(sparams, SNDRV_PCM_HW_PARAM_FORMAT); + if (direct) + sformat = format; + else + sformat = snd_pcm_plug_slave_format(format, &sformat_mask); + + if (sformat < 0 || !snd_mask_test(&sformat_mask, sformat)) { + for (sformat = 0; sformat <= SNDRV_PCM_FORMAT_LAST; sformat++) { + if (snd_mask_test(&sformat_mask, sformat) && + snd_pcm_oss_format_to(sformat) >= 0) + break; + } + if (sformat > SNDRV_PCM_FORMAT_LAST) { + snd_printd("Cannot find a format!!!\n"); + err = -EINVAL; + goto failure; + } + } + err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, sformat, 0); + snd_assert(err >= 0, goto failure); + + if (direct) { + memcpy(params, sparams, sizeof(*params)); + } else { + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, + SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, + snd_pcm_oss_format_from(runtime->oss.format), 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, + runtime->oss.rate, 0); + pdprintf("client: access = %i, format = %i, channels = %i, rate = %i\n", + params_access(params), params_format(params), + params_channels(params), params_rate(params)); + } + pdprintf("slave: access = %i, format = %i, channels = %i, rate = %i\n", + params_access(sparams), params_format(sparams), + params_channels(sparams), params_rate(sparams)); + + oss_frame_size = snd_pcm_format_physical_width(params_format(params)) * + params_channels(params) / 8; + + snd_pcm_oss_plugin_clear(substream); + if (!direct) { + /* add necessary plugins */ + snd_pcm_oss_plugin_clear(substream); + if ((err = snd_pcm_plug_format_plugins(substream, + params, + sparams)) < 0) { + snd_printd("snd_pcm_plug_format_plugins failed: %i\n", err); + snd_pcm_oss_plugin_clear(substream); + goto failure; + } + if (runtime->oss.plugin_first) { + snd_pcm_plugin_t *plugin; + if ((err = snd_pcm_plugin_build_io(substream, sparams, &plugin)) < 0) { + snd_printd("snd_pcm_plugin_build_io failed: %i\n", err); + snd_pcm_oss_plugin_clear(substream); + goto failure; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + err = snd_pcm_plugin_append(plugin); + } else { + err = snd_pcm_plugin_insert(plugin); + } + if (err < 0) { + snd_pcm_oss_plugin_clear(substream); + goto failure; + } + } + } + + err = snd_pcm_oss_period_size(substream, params, sparams); + if (err < 0) + goto failure; + + n = snd_pcm_plug_slave_size(substream, runtime->oss.period_bytes / oss_frame_size); + err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, n, NULL); + snd_assert(err >= 0, goto failure); + + err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIODS, + runtime->oss.periods, NULL); + snd_assert(err >= 0, goto failure); + + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + + if ((err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, sparams)) < 0) { + snd_printd("HW_PARAMS failed: %i\n", err); + goto failure; + } + + memset(sw_params, 0, sizeof(*sw_params)); + if (runtime->oss.trigger) { + sw_params->start_threshold = 1; + } else { + sw_params->start_threshold = runtime->boundary; + } + if (atomic_read(&runtime->mmap_count) || substream->stream == SNDRV_PCM_STREAM_CAPTURE) + sw_params->stop_threshold = runtime->boundary; + else + sw_params->stop_threshold = runtime->buffer_size; + sw_params->tstamp_mode = SNDRV_PCM_TSTAMP_NONE; + sw_params->period_step = 1; + sw_params->sleep_min = 0; + sw_params->avail_min = 1; + sw_params->xfer_align = 1; + if (atomic_read(&runtime->mmap_count) || + (substream->oss.setup && substream->oss.setup->nosilence)) { + sw_params->silence_threshold = 0; + sw_params->silence_size = 0; + } else { + snd_pcm_uframes_t frames; + frames = runtime->period_size + 16; + if (frames > runtime->buffer_size) + frames = runtime->buffer_size; + sw_params->silence_threshold = frames; + sw_params->silence_size = frames; + } + + if ((err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_SW_PARAMS, sw_params)) < 0) { + snd_printd("SW_PARAMS failed: %i\n", err); + goto failure; + } + + runtime->oss.periods = params_periods(sparams); + oss_period_size = snd_pcm_plug_client_size(substream, params_period_size(sparams)); + snd_assert(oss_period_size >= 0, err = -EINVAL; goto failure); + if (runtime->oss.plugin_first) { + err = snd_pcm_plug_alloc(substream, oss_period_size); + if (err < 0) + goto failure; + } + oss_period_size *= oss_frame_size; + + oss_buffer_size = oss_period_size * runtime->oss.periods; + snd_assert(oss_buffer_size >= 0, err = -EINVAL; goto failure); + + runtime->oss.period_bytes = oss_period_size; + runtime->oss.buffer_bytes = oss_buffer_size; + + pdprintf("oss: period bytes = %i, buffer bytes = %i\n", + runtime->oss.period_bytes, + runtime->oss.buffer_bytes); + pdprintf("slave: period_size = %i, buffer_size = %i\n", + params_period_size(sparams), + params_buffer_size(sparams)); + + runtime->oss.format = snd_pcm_oss_format_to(params_format(params)); + runtime->oss.channels = params_channels(params); + runtime->oss.rate = params_rate(params); + + runtime->oss.params = 0; + runtime->oss.prepare = 1; + vfree(runtime->oss.buffer); + runtime->oss.buffer = vmalloc(runtime->oss.period_bytes); + runtime->oss.buffer_used = 0; + if (runtime->dma_area) + snd_pcm_format_set_silence(runtime->format, runtime->dma_area, bytes_to_samples(runtime, runtime->dma_bytes)); + + runtime->oss.period_frames = snd_pcm_alsa_frames(substream, oss_period_size); + + err = 0; +failure: + kfree(sw_params); + kfree(params); + kfree(sparams); + return err; +} + +static int snd_pcm_oss_get_active_substream(snd_pcm_oss_file_t *pcm_oss_file, snd_pcm_substream_t **r_substream) +{ + int idx, err; + snd_pcm_substream_t *asubstream = NULL, *substream; + + for (idx = 0; idx < 2; idx++) { + substream = pcm_oss_file->streams[idx]; + if (substream == NULL) + continue; + if (asubstream == NULL) + asubstream = substream; + if (substream->runtime->oss.params) { + err = snd_pcm_oss_change_params(substream); + if (err < 0) + return err; + } + } + snd_assert(asubstream != NULL, return -EIO); + if (r_substream) + *r_substream = asubstream; + return 0; +} + +static int snd_pcm_oss_prepare(snd_pcm_substream_t *substream) +{ + int err; + snd_pcm_runtime_t *runtime = substream->runtime; + + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL); + if (err < 0) { + snd_printd("snd_pcm_oss_prepare: SNDRV_PCM_IOCTL_PREPARE failed\n"); + return err; + } + runtime->oss.prepare = 0; + runtime->oss.prev_hw_ptr_interrupt = 0; + runtime->oss.period_ptr = 0; + runtime->oss.buffer_used = 0; + + return 0; +} + +static int snd_pcm_oss_make_ready(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime; + int err; + + if (substream == NULL) + return 0; + runtime = substream->runtime; + if (runtime->oss.params) { + err = snd_pcm_oss_change_params(substream); + if (err < 0) + return err; + } + if (runtime->oss.prepare) { + err = snd_pcm_oss_prepare(substream); + if (err < 0) + return err; + } + return 0; +} + +static int snd_pcm_oss_capture_position_fixup(snd_pcm_substream_t *substream, snd_pcm_sframes_t *delay) +{ + snd_pcm_runtime_t *runtime; + snd_pcm_uframes_t frames; + int err = 0; + + while (1) { + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, delay); + if (err < 0) + break; + runtime = substream->runtime; + if (*delay <= (snd_pcm_sframes_t)runtime->buffer_size) + break; + /* in case of overrun, skip whole periods like OSS/Linux driver does */ + /* until avail(delay) <= buffer_size */ + frames = (*delay - runtime->buffer_size) + runtime->period_size - 1; + frames /= runtime->period_size; + frames *= runtime->period_size; + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_FORWARD, &frames); + if (err < 0) + break; + } + return err; +} + +snd_pcm_sframes_t snd_pcm_oss_write3(snd_pcm_substream_t *substream, const char *ptr, snd_pcm_uframes_t frames, int in_kernel) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int ret; + while (1) { + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + if (runtime->status->state == SNDRV_PCM_STATE_XRUN) + printk("pcm_oss: write: recovering from XRUN\n"); + else + printk("pcm_oss: write: recovering from SUSPEND\n"); +#endif + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + if (in_kernel) { + mm_segment_t fs; + fs = snd_enter_user(); + ret = snd_pcm_lib_write(substream, (void __user *)ptr, frames); + snd_leave_user(fs); + } else { + ret = snd_pcm_lib_write(substream, (void __user *)ptr, frames); + } + if (ret != -EPIPE && ret != -ESTRPIPE) + break; + /* test, if we can't store new data, because the stream */ + /* has not been started */ + if (runtime->status->state == SNDRV_PCM_STATE_PREPARED) + return -EAGAIN; + } + return ret; +} + +snd_pcm_sframes_t snd_pcm_oss_read3(snd_pcm_substream_t *substream, char *ptr, snd_pcm_uframes_t frames, int in_kernel) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_sframes_t delay; + int ret; + while (1) { + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + if (runtime->status->state == SNDRV_PCM_STATE_XRUN) + printk("pcm_oss: read: recovering from XRUN\n"); + else + printk("pcm_oss: read: recovering from SUSPEND\n"); +#endif + ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); + if (ret < 0) + break; + } else if (runtime->status->state == SNDRV_PCM_STATE_SETUP) { + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + ret = snd_pcm_oss_capture_position_fixup(substream, &delay); + if (ret < 0) + break; + if (in_kernel) { + mm_segment_t fs; + fs = snd_enter_user(); + ret = snd_pcm_lib_read(substream, (void __user *)ptr, frames); + snd_leave_user(fs); + } else { + ret = snd_pcm_lib_read(substream, (void __user *)ptr, frames); + } + if (ret == -EPIPE) { + if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) { + ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + if (ret < 0) + break; + } + continue; + } + if (ret != -ESTRPIPE) + break; + } + return ret; +} + +snd_pcm_sframes_t snd_pcm_oss_writev3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int ret; + while (1) { + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + if (runtime->status->state == SNDRV_PCM_STATE_XRUN) + printk("pcm_oss: writev: recovering from XRUN\n"); + else + printk("pcm_oss: writev: recovering from SUSPEND\n"); +#endif + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + if (in_kernel) { + mm_segment_t fs; + fs = snd_enter_user(); + ret = snd_pcm_lib_writev(substream, (void __user **)bufs, frames); + snd_leave_user(fs); + } else { + ret = snd_pcm_lib_writev(substream, (void __user **)bufs, frames); + } + if (ret != -EPIPE && ret != -ESTRPIPE) + break; + + /* test, if we can't store new data, because the stream */ + /* has not been started */ + if (runtime->status->state == SNDRV_PCM_STATE_PREPARED) + return -EAGAIN; + } + return ret; +} + +snd_pcm_sframes_t snd_pcm_oss_readv3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int ret; + while (1) { + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + if (runtime->status->state == SNDRV_PCM_STATE_XRUN) + printk("pcm_oss: readv: recovering from XRUN\n"); + else + printk("pcm_oss: readv: recovering from SUSPEND\n"); +#endif + ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); + if (ret < 0) + break; + } else if (runtime->status->state == SNDRV_PCM_STATE_SETUP) { + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + if (in_kernel) { + mm_segment_t fs; + fs = snd_enter_user(); + ret = snd_pcm_lib_readv(substream, (void __user **)bufs, frames); + snd_leave_user(fs); + } else { + ret = snd_pcm_lib_readv(substream, (void __user **)bufs, frames); + } + if (ret != -EPIPE && ret != -ESTRPIPE) + break; + } + return ret; +} + +static ssize_t snd_pcm_oss_write2(snd_pcm_substream_t *substream, const char *buf, size_t bytes, int in_kernel) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_sframes_t frames, frames1; + if (runtime->oss.plugin_first) { + snd_pcm_plugin_channel_t *channels; + size_t oss_frame_bytes = (runtime->oss.plugin_first->src_width * runtime->oss.plugin_first->src_format.channels) / 8; + if (!in_kernel) { + if (copy_from_user(runtime->oss.buffer, (const char __user *)buf, bytes)) + return -EFAULT; + buf = runtime->oss.buffer; + } + frames = bytes / oss_frame_bytes; + frames1 = snd_pcm_plug_client_channels_buf(substream, (char *)buf, frames, &channels); + if (frames1 < 0) + return frames1; + frames1 = snd_pcm_plug_write_transfer(substream, channels, frames1); + if (frames1 <= 0) + return frames1; + bytes = frames1 * oss_frame_bytes; + } else { + frames = bytes_to_frames(runtime, bytes); + frames1 = snd_pcm_oss_write3(substream, buf, frames, in_kernel); + if (frames1 <= 0) + return frames1; + bytes = frames_to_bytes(runtime, frames1); + } + return bytes; +} + +static ssize_t snd_pcm_oss_write1(snd_pcm_substream_t *substream, const char __user *buf, size_t bytes) +{ + size_t xfer = 0; + ssize_t tmp; + snd_pcm_runtime_t *runtime = substream->runtime; + + if (atomic_read(&runtime->mmap_count)) + return -ENXIO; + + if ((tmp = snd_pcm_oss_make_ready(substream)) < 0) + return tmp; + while (bytes > 0) { + if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) { + tmp = bytes; + if (tmp + runtime->oss.buffer_used > runtime->oss.period_bytes) + tmp = runtime->oss.period_bytes - runtime->oss.buffer_used; + if (tmp > 0) { + if (copy_from_user(runtime->oss.buffer + runtime->oss.buffer_used, buf, tmp)) + return xfer > 0 ? (snd_pcm_sframes_t)xfer : -EFAULT; + } + runtime->oss.buffer_used += tmp; + buf += tmp; + bytes -= tmp; + xfer += tmp; + if ((substream->oss.setup != NULL && substream->oss.setup->partialfrag) || + runtime->oss.buffer_used == runtime->oss.period_bytes) { + tmp = snd_pcm_oss_write2(substream, runtime->oss.buffer + runtime->oss.period_ptr, + runtime->oss.buffer_used - runtime->oss.period_ptr, 1); + if (tmp <= 0) + return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp; + runtime->oss.bytes += tmp; + runtime->oss.period_ptr += tmp; + runtime->oss.period_ptr %= runtime->oss.period_bytes; + if (runtime->oss.period_ptr == 0 || + runtime->oss.period_ptr == runtime->oss.buffer_used) + runtime->oss.buffer_used = 0; + else if ((substream->ffile->f_flags & O_NONBLOCK) != 0) + return xfer > 0 ? xfer : -EAGAIN; + } + } else { + tmp = snd_pcm_oss_write2(substream, (const char *)buf, runtime->oss.period_bytes, 0); + if (tmp <= 0) + return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp; + runtime->oss.bytes += tmp; + buf += tmp; + bytes -= tmp; + xfer += tmp; + if ((substream->ffile->f_flags & O_NONBLOCK) != 0 && + tmp != runtime->oss.period_bytes) + break; + } + } + return xfer; +} + +static ssize_t snd_pcm_oss_read2(snd_pcm_substream_t *substream, char *buf, size_t bytes, int in_kernel) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_sframes_t frames, frames1; + char __user *final_dst = (char __user *)buf; + if (runtime->oss.plugin_first) { + snd_pcm_plugin_channel_t *channels; + size_t oss_frame_bytes = (runtime->oss.plugin_last->dst_width * runtime->oss.plugin_last->dst_format.channels) / 8; + if (!in_kernel) + buf = runtime->oss.buffer; + frames = bytes / oss_frame_bytes; + frames1 = snd_pcm_plug_client_channels_buf(substream, buf, frames, &channels); + if (frames1 < 0) + return frames1; + frames1 = snd_pcm_plug_read_transfer(substream, channels, frames1); + if (frames1 <= 0) + return frames1; + bytes = frames1 * oss_frame_bytes; + if (!in_kernel && copy_to_user(final_dst, buf, bytes)) + return -EFAULT; + } else { + frames = bytes_to_frames(runtime, bytes); + frames1 = snd_pcm_oss_read3(substream, buf, frames, in_kernel); + if (frames1 <= 0) + return frames1; + bytes = frames_to_bytes(runtime, frames1); + } + return bytes; +} + +static ssize_t snd_pcm_oss_read1(snd_pcm_substream_t *substream, char __user *buf, size_t bytes) +{ + size_t xfer = 0; + ssize_t tmp; + snd_pcm_runtime_t *runtime = substream->runtime; + + if (atomic_read(&runtime->mmap_count)) + return -ENXIO; + + if ((tmp = snd_pcm_oss_make_ready(substream)) < 0) + return tmp; + while (bytes > 0) { + if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) { + if (runtime->oss.buffer_used == 0) { + tmp = snd_pcm_oss_read2(substream, runtime->oss.buffer, runtime->oss.period_bytes, 1); + if (tmp <= 0) + return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp; + runtime->oss.bytes += tmp; + runtime->oss.period_ptr = tmp; + runtime->oss.buffer_used = tmp; + } + tmp = bytes; + if ((size_t) tmp > runtime->oss.buffer_used) + tmp = runtime->oss.buffer_used; + if (copy_to_user(buf, runtime->oss.buffer + (runtime->oss.period_ptr - runtime->oss.buffer_used), tmp)) + return xfer > 0 ? (snd_pcm_sframes_t)xfer : -EFAULT; + buf += tmp; + bytes -= tmp; + xfer += tmp; + runtime->oss.buffer_used -= tmp; + } else { + tmp = snd_pcm_oss_read2(substream, (char *)buf, runtime->oss.period_bytes, 0); + if (tmp <= 0) + return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp; + runtime->oss.bytes += tmp; + buf += tmp; + bytes -= tmp; + xfer += tmp; + } + } + return xfer; +} + +static int snd_pcm_oss_reset(snd_pcm_oss_file_t *pcm_oss_file) +{ + snd_pcm_substream_t *substream; + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream != NULL) { + snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + substream->runtime->oss.prepare = 1; + } + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (substream != NULL) { + snd_pcm_kernel_capture_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + substream->runtime->oss.prepare = 1; + } + return 0; +} + +static int snd_pcm_oss_post(snd_pcm_oss_file_t *pcm_oss_file) +{ + snd_pcm_substream_t *substream; + int err; + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream != NULL) { + if ((err = snd_pcm_oss_make_ready(substream)) < 0) + return err; + snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_START, NULL); + } + /* note: all errors from the start action are ignored */ + /* OSS apps do not know, how to handle them */ + return 0; +} + +static int snd_pcm_oss_sync1(snd_pcm_substream_t *substream, size_t size) +{ + snd_pcm_runtime_t *runtime; + ssize_t result = 0; + long res; + wait_queue_t wait; + + runtime = substream->runtime; + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); +#ifdef OSS_DEBUG + printk("sync1: size = %li\n", size); +#endif + while (1) { + result = snd_pcm_oss_write2(substream, runtime->oss.buffer, size, 1); + if (result > 0) { + runtime->oss.buffer_used = 0; + result = 0; + break; + } + if (result != 0 && result != -EAGAIN) + break; + result = 0; + set_current_state(TASK_INTERRUPTIBLE); + snd_pcm_stream_lock_irq(substream); + res = runtime->status->state; + snd_pcm_stream_unlock_irq(substream); + if (res != SNDRV_PCM_STATE_RUNNING) { + set_current_state(TASK_RUNNING); + break; + } + res = schedule_timeout(10 * HZ); + if (signal_pending(current)) { + result = -ERESTARTSYS; + break; + } + if (res == 0) { + snd_printk(KERN_ERR "OSS sync error - DMA timeout\n"); + result = -EIO; + break; + } + } + remove_wait_queue(&runtime->sleep, &wait); + return result; +} + +static int snd_pcm_oss_sync(snd_pcm_oss_file_t *pcm_oss_file) +{ + int err = 0; + unsigned int saved_f_flags; + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + snd_pcm_format_t format; + unsigned long width; + size_t size; + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream != NULL) { + runtime = substream->runtime; + if (atomic_read(&runtime->mmap_count)) + goto __direct; + if ((err = snd_pcm_oss_make_ready(substream)) < 0) + return err; + format = snd_pcm_oss_format_from(runtime->oss.format); + width = snd_pcm_format_physical_width(format); + if (runtime->oss.buffer_used > 0) { +#ifdef OSS_DEBUG + printk("sync: buffer_used\n"); +#endif + size = (8 * (runtime->oss.period_bytes - runtime->oss.buffer_used) + 7) / width; + snd_pcm_format_set_silence(format, + runtime->oss.buffer + runtime->oss.buffer_used, + size); + err = snd_pcm_oss_sync1(substream, runtime->oss.period_bytes); + if (err < 0) + return err; + } else if (runtime->oss.period_ptr > 0) { +#ifdef OSS_DEBUG + printk("sync: period_ptr\n"); +#endif + size = runtime->oss.period_bytes - runtime->oss.period_ptr; + snd_pcm_format_set_silence(format, + runtime->oss.buffer, + size * 8 / width); + err = snd_pcm_oss_sync1(substream, size); + if (err < 0) + return err; + } + /* + * The ALSA's period might be a bit large than OSS one. + * Fill the remain portion of ALSA period with zeros. + */ + size = runtime->control->appl_ptr % runtime->period_size; + if (size > 0) { + size = runtime->period_size - size; + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + size = (runtime->frame_bits * size) / 8; + while (size > 0) { + mm_segment_t fs; + size_t size1 = size < runtime->oss.period_bytes ? size : runtime->oss.period_bytes; + size -= size1; + size1 *= 8; + size1 /= runtime->sample_bits; + snd_pcm_format_set_silence(runtime->format, + runtime->oss.buffer, + size1); + fs = snd_enter_user(); + snd_pcm_lib_write(substream, (void __user *)runtime->oss.buffer, size1); + snd_leave_user(fs); + } + } else if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) { + void __user *buffers[runtime->channels]; + memset(buffers, 0, runtime->channels * sizeof(void *)); + snd_pcm_lib_writev(substream, buffers, size); + } + } + /* + * finish sync: drain the buffer + */ + __direct: + saved_f_flags = substream->ffile->f_flags; + substream->ffile->f_flags &= ~O_NONBLOCK; + err = snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); + substream->ffile->f_flags = saved_f_flags; + if (err < 0) + return err; + runtime->oss.prepare = 1; + } + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (substream != NULL) { + if ((err = snd_pcm_oss_make_ready(substream)) < 0) + return err; + runtime = substream->runtime; + err = snd_pcm_kernel_capture_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + if (err < 0) + return err; + runtime->oss.buffer_used = 0; + runtime->oss.prepare = 1; + } + return 0; +} + +static int snd_pcm_oss_set_rate(snd_pcm_oss_file_t *pcm_oss_file, int rate) +{ + int idx; + + for (idx = 1; idx >= 0; --idx) { + snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; + snd_pcm_runtime_t *runtime; + if (substream == NULL) + continue; + runtime = substream->runtime; + if (rate < 1000) + rate = 1000; + else if (rate > 192000) + rate = 192000; + if (runtime->oss.rate != rate) { + runtime->oss.params = 1; + runtime->oss.rate = rate; + } + } + return snd_pcm_oss_get_rate(pcm_oss_file); +} + +static int snd_pcm_oss_get_rate(snd_pcm_oss_file_t *pcm_oss_file) +{ + snd_pcm_substream_t *substream; + int err; + + if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) + return err; + return substream->runtime->oss.rate; +} + +static int snd_pcm_oss_set_channels(snd_pcm_oss_file_t *pcm_oss_file, unsigned int channels) +{ + int idx; + if (channels < 1) + channels = 1; + if (channels > 128) + return -EINVAL; + for (idx = 1; idx >= 0; --idx) { + snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; + snd_pcm_runtime_t *runtime; + if (substream == NULL) + continue; + runtime = substream->runtime; + if (runtime->oss.channels != channels) { + runtime->oss.params = 1; + runtime->oss.channels = channels; + } + } + return snd_pcm_oss_get_channels(pcm_oss_file); +} + +static int snd_pcm_oss_get_channels(snd_pcm_oss_file_t *pcm_oss_file) +{ + snd_pcm_substream_t *substream; + int err; + + if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) + return err; + return substream->runtime->oss.channels; +} + +static int snd_pcm_oss_get_block_size(snd_pcm_oss_file_t *pcm_oss_file) +{ + snd_pcm_substream_t *substream; + int err; + + if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) + return err; + return substream->runtime->oss.period_bytes; +} + +static int snd_pcm_oss_get_formats(snd_pcm_oss_file_t *pcm_oss_file) +{ + snd_pcm_substream_t *substream; + int err; + int direct; + snd_pcm_hw_params_t *params; + unsigned int formats = 0; + snd_mask_t format_mask; + int fmt; + + if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) + return err; + if (atomic_read(&substream->runtime->mmap_count)) { + direct = 1; + } else { + snd_pcm_oss_setup_t *setup = substream->oss.setup; + direct = (setup != NULL && setup->direct); + } + if (!direct) + return AFMT_MU_LAW | AFMT_U8 | + AFMT_S16_LE | AFMT_S16_BE | + AFMT_S8 | AFMT_U16_LE | + AFMT_U16_BE; + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + _snd_pcm_hw_params_any(params); + err = snd_pcm_hw_refine(substream, params); + format_mask = *hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + kfree(params); + snd_assert(err >= 0, return err); + for (fmt = 0; fmt < 32; ++fmt) { + if (snd_mask_test(&format_mask, fmt)) { + int f = snd_pcm_oss_format_to(fmt); + if (f >= 0) + formats |= f; + } + } + return formats; +} + +static int snd_pcm_oss_set_format(snd_pcm_oss_file_t *pcm_oss_file, int format) +{ + int formats, idx; + + if (format != AFMT_QUERY) { + formats = snd_pcm_oss_get_formats(pcm_oss_file); + if (!(formats & format)) + format = AFMT_U8; + for (idx = 1; idx >= 0; --idx) { + snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; + snd_pcm_runtime_t *runtime; + if (substream == NULL) + continue; + runtime = substream->runtime; + if (runtime->oss.format != format) { + runtime->oss.params = 1; + runtime->oss.format = format; + } + } + } + return snd_pcm_oss_get_format(pcm_oss_file); +} + +static int snd_pcm_oss_get_format(snd_pcm_oss_file_t *pcm_oss_file) +{ + snd_pcm_substream_t *substream; + int err; + + if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) + return err; + return substream->runtime->oss.format; +} + +static int snd_pcm_oss_set_subdivide1(snd_pcm_substream_t *substream, int subdivide) +{ + snd_pcm_runtime_t *runtime; + + if (substream == NULL) + return 0; + runtime = substream->runtime; + if (subdivide == 0) { + subdivide = runtime->oss.subdivision; + if (subdivide == 0) + subdivide = 1; + return subdivide; + } + if (runtime->oss.subdivision || runtime->oss.fragshift) + return -EINVAL; + if (subdivide != 1 && subdivide != 2 && subdivide != 4 && + subdivide != 8 && subdivide != 16) + return -EINVAL; + runtime->oss.subdivision = subdivide; + runtime->oss.params = 1; + return subdivide; +} + +static int snd_pcm_oss_set_subdivide(snd_pcm_oss_file_t *pcm_oss_file, int subdivide) +{ + int err = -EINVAL, idx; + + for (idx = 1; idx >= 0; --idx) { + snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; + if (substream == NULL) + continue; + if ((err = snd_pcm_oss_set_subdivide1(substream, subdivide)) < 0) + return err; + } + return err; +} + +static int snd_pcm_oss_set_fragment1(snd_pcm_substream_t *substream, unsigned int val) +{ + snd_pcm_runtime_t *runtime; + + if (substream == NULL) + return 0; + runtime = substream->runtime; + if (runtime->oss.subdivision || runtime->oss.fragshift) + return -EINVAL; + runtime->oss.fragshift = val & 0xffff; + runtime->oss.maxfrags = (val >> 16) & 0xffff; + if (runtime->oss.fragshift < 4) /* < 16 */ + runtime->oss.fragshift = 4; + if (runtime->oss.maxfrags < 2) + runtime->oss.maxfrags = 2; + runtime->oss.params = 1; + return 0; +} + +static int snd_pcm_oss_set_fragment(snd_pcm_oss_file_t *pcm_oss_file, unsigned int val) +{ + int err = -EINVAL, idx; + + for (idx = 1; idx >= 0; --idx) { + snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; + if (substream == NULL) + continue; + if ((err = snd_pcm_oss_set_fragment1(substream, val)) < 0) + return err; + } + return err; +} + +static int snd_pcm_oss_nonblock(struct file * file) +{ + file->f_flags |= O_NONBLOCK; + return 0; +} + +static int snd_pcm_oss_get_caps1(snd_pcm_substream_t *substream, int res) +{ + + if (substream == NULL) { + res &= ~DSP_CAP_DUPLEX; + return res; + } +#ifdef DSP_CAP_MULTI + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + if (substream->pstr->substream_count > 1) + res |= DSP_CAP_MULTI; +#endif + /* DSP_CAP_REALTIME is set all times: */ + /* all ALSA drivers can return actual pointer in ring buffer */ +#if defined(DSP_CAP_REALTIME) && 0 + { + snd_pcm_runtime_t *runtime = substream->runtime; + if (runtime->info & (SNDRV_PCM_INFO_BLOCK_TRANSFER|SNDRV_PCM_INFO_BATCH)) + res &= ~DSP_CAP_REALTIME; + } +#endif + return res; +} + +static int snd_pcm_oss_get_caps(snd_pcm_oss_file_t *pcm_oss_file) +{ + int result, idx; + + result = DSP_CAP_TRIGGER | DSP_CAP_MMAP | DSP_CAP_DUPLEX | DSP_CAP_REALTIME; + for (idx = 0; idx < 2; idx++) { + snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; + result = snd_pcm_oss_get_caps1(substream, result); + } + result |= 0x0001; /* revision - same as SB AWE 64 */ + return result; +} + +static void snd_pcm_oss_simulate_fill(snd_pcm_substream_t *substream, snd_pcm_uframes_t hw_ptr) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr; + appl_ptr = hw_ptr + runtime->buffer_size; + appl_ptr %= runtime->boundary; + runtime->control->appl_ptr = appl_ptr; +} + +static int snd_pcm_oss_set_trigger(snd_pcm_oss_file_t *pcm_oss_file, int trigger) +{ + snd_pcm_runtime_t *runtime; + snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL; + int err, cmd; + +#ifdef OSS_DEBUG + printk("pcm_oss: trigger = 0x%x\n", trigger); +#endif + + psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + + if (psubstream) { + if ((err = snd_pcm_oss_make_ready(psubstream)) < 0) + return err; + } + if (csubstream) { + if ((err = snd_pcm_oss_make_ready(csubstream)) < 0) + return err; + } + if (psubstream) { + runtime = psubstream->runtime; + if (trigger & PCM_ENABLE_OUTPUT) { + if (runtime->oss.trigger) + goto _skip1; + if (atomic_read(&psubstream->runtime->mmap_count)) + snd_pcm_oss_simulate_fill(psubstream, runtime->hw_ptr_interrupt); + runtime->oss.trigger = 1; + runtime->start_threshold = 1; + cmd = SNDRV_PCM_IOCTL_START; + } else { + if (!runtime->oss.trigger) + goto _skip1; + runtime->oss.trigger = 0; + runtime->start_threshold = runtime->boundary; + cmd = SNDRV_PCM_IOCTL_DROP; + runtime->oss.prepare = 1; + } + err = snd_pcm_kernel_playback_ioctl(psubstream, cmd, NULL); + if (err < 0) + return err; + } + _skip1: + if (csubstream) { + runtime = csubstream->runtime; + if (trigger & PCM_ENABLE_INPUT) { + if (runtime->oss.trigger) + goto _skip2; + runtime->oss.trigger = 1; + runtime->start_threshold = 1; + cmd = SNDRV_PCM_IOCTL_START; + } else { + if (!runtime->oss.trigger) + goto _skip2; + runtime->oss.trigger = 0; + runtime->start_threshold = runtime->boundary; + cmd = SNDRV_PCM_IOCTL_DROP; + runtime->oss.prepare = 1; + } + err = snd_pcm_kernel_capture_ioctl(csubstream, cmd, NULL); + if (err < 0) + return err; + } + _skip2: + return 0; +} + +static int snd_pcm_oss_get_trigger(snd_pcm_oss_file_t *pcm_oss_file) +{ + snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL; + int result = 0; + + psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (psubstream && psubstream->runtime && psubstream->runtime->oss.trigger) + result |= PCM_ENABLE_OUTPUT; + if (csubstream && csubstream->runtime && csubstream->runtime->oss.trigger) + result |= PCM_ENABLE_INPUT; + return result; +} + +static int snd_pcm_oss_get_odelay(snd_pcm_oss_file_t *pcm_oss_file) +{ + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + snd_pcm_sframes_t delay; + int err; + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream == NULL) + return -EINVAL; + if ((err = snd_pcm_oss_make_ready(substream)) < 0) + return err; + runtime = substream->runtime; + if (runtime->oss.params || runtime->oss.prepare) + return 0; + err = snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay); + if (err == -EPIPE) + delay = 0; /* hack for broken OSS applications */ + else if (err < 0) + return err; + return snd_pcm_oss_bytes(substream, delay); +} + +static int snd_pcm_oss_get_ptr(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct count_info __user * _info) +{ + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + snd_pcm_sframes_t delay; + int fixup; + struct count_info info; + int err; + + if (_info == NULL) + return -EFAULT; + substream = pcm_oss_file->streams[stream]; + if (substream == NULL) + return -EINVAL; + if ((err = snd_pcm_oss_make_ready(substream)) < 0) + return err; + runtime = substream->runtime; + if (runtime->oss.params || runtime->oss.prepare) { + memset(&info, 0, sizeof(info)); + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay); + if (err == -EPIPE || err == -ESTRPIPE || (! err && delay < 0)) { + err = 0; + delay = 0; + fixup = 0; + } else { + fixup = runtime->oss.buffer_used; + } + } else { + err = snd_pcm_oss_capture_position_fixup(substream, &delay); + fixup = -runtime->oss.buffer_used; + } + if (err < 0) + return err; + info.ptr = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr % runtime->buffer_size); + if (atomic_read(&runtime->mmap_count)) { + snd_pcm_sframes_t n; + n = (delay = runtime->hw_ptr_interrupt) - runtime->oss.prev_hw_ptr_interrupt; + if (n < 0) + n += runtime->boundary; + info.blocks = n / runtime->period_size; + runtime->oss.prev_hw_ptr_interrupt = delay; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_pcm_oss_simulate_fill(substream, delay); + info.bytes = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr) & INT_MAX; + } else { + delay = snd_pcm_oss_bytes(substream, delay) + fixup; + info.blocks = delay / runtime->oss.period_bytes; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + info.bytes = (runtime->oss.bytes - delay) & INT_MAX; + else + info.bytes = (runtime->oss.bytes + delay) & INT_MAX; + } + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_pcm_oss_get_space(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct audio_buf_info __user *_info) +{ + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + snd_pcm_sframes_t avail; + int fixup; + struct audio_buf_info info; + int err; + + if (_info == NULL) + return -EFAULT; + substream = pcm_oss_file->streams[stream]; + if (substream == NULL) + return -EINVAL; + runtime = substream->runtime; + + if (runtime->oss.params && + (err = snd_pcm_oss_change_params(substream)) < 0) + return err; + + info.fragsize = runtime->oss.period_bytes; + info.fragstotal = runtime->periods; + if (runtime->oss.prepare) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + info.bytes = runtime->oss.period_bytes * runtime->oss.periods; + info.fragments = runtime->oss.periods; + } else { + info.bytes = 0; + info.fragments = 0; + } + } else { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &avail); + if (err == -EPIPE || err == -ESTRPIPE || (! err && avail < 0)) { + avail = runtime->buffer_size; + err = 0; + fixup = 0; + } else { + avail = runtime->buffer_size - avail; + fixup = -runtime->oss.buffer_used; + } + } else { + err = snd_pcm_oss_capture_position_fixup(substream, &avail); + fixup = runtime->oss.buffer_used; + } + if (err < 0) + return err; + info.bytes = snd_pcm_oss_bytes(substream, avail) + fixup; + info.fragments = info.bytes / runtime->oss.period_bytes; + } + +#ifdef OSS_DEBUG + printk("pcm_oss: space: bytes = %i, fragments = %i, fragstotal = %i, fragsize = %i\n", info.bytes, info.fragments, info.fragstotal, info.fragsize); +#endif + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_pcm_oss_get_mapbuf(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct buffmem_desc __user * _info) +{ + // it won't be probably implemented + // snd_printd("TODO: snd_pcm_oss_get_mapbuf\n"); + return -EINVAL; +} + +static snd_pcm_oss_setup_t *snd_pcm_oss_look_for_setup(snd_pcm_t *pcm, int stream, const char *task_name) +{ + const char *ptr, *ptrl; + snd_pcm_oss_setup_t *setup; + + down(&pcm->streams[stream].oss.setup_mutex); + for (setup = pcm->streams[stream].oss.setup_list; setup; setup = setup->next) { + if (!strcmp(setup->task_name, task_name)) { + up(&pcm->streams[stream].oss.setup_mutex); + return setup; + } + } + ptr = ptrl = task_name; + while (*ptr) { + if (*ptr == '/') + ptrl = ptr + 1; + ptr++; + } + if (ptrl == task_name) { + goto __not_found; + return NULL; + } + for (setup = pcm->streams[stream].oss.setup_list; setup; setup = setup->next) { + if (!strcmp(setup->task_name, ptrl)) { + up(&pcm->streams[stream].oss.setup_mutex); + return setup; + } + } + __not_found: + up(&pcm->streams[stream].oss.setup_mutex); + return NULL; +} + +static void snd_pcm_oss_init_substream(snd_pcm_substream_t *substream, + snd_pcm_oss_setup_t *setup, + int minor) +{ + snd_pcm_runtime_t *runtime; + + substream->oss.oss = 1; + substream->oss.setup = setup; + runtime = substream->runtime; + runtime->oss.params = 1; + runtime->oss.trigger = 1; + runtime->oss.rate = 8000; + switch (SNDRV_MINOR_OSS_DEVICE(minor)) { + case SNDRV_MINOR_OSS_PCM_8: + runtime->oss.format = AFMT_U8; + break; + case SNDRV_MINOR_OSS_PCM_16: + runtime->oss.format = AFMT_S16_LE; + break; + default: + runtime->oss.format = AFMT_MU_LAW; + } + runtime->oss.channels = 1; + runtime->oss.fragshift = 0; + runtime->oss.maxfrags = 0; + runtime->oss.subdivision = 0; +} + +static void snd_pcm_oss_release_substream(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime; + runtime = substream->runtime; + vfree(runtime->oss.buffer); + snd_pcm_oss_plugin_clear(substream); + substream->oss.file = NULL; + substream->oss.oss = 0; +} + +static int snd_pcm_oss_release_file(snd_pcm_oss_file_t *pcm_oss_file) +{ + int cidx; + snd_assert(pcm_oss_file != NULL, return -ENXIO); + for (cidx = 0; cidx < 2; ++cidx) { + snd_pcm_substream_t *substream = pcm_oss_file->streams[cidx]; + snd_pcm_runtime_t *runtime; + if (substream == NULL) + continue; + runtime = substream->runtime; + + snd_pcm_stream_lock_irq(substream); + if (snd_pcm_running(substream)) + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + snd_pcm_stream_unlock_irq(substream); + if (substream->open_flag) { + if (substream->ops->hw_free != NULL) + substream->ops->hw_free(substream); + substream->ops->close(substream); + substream->open_flag = 0; + } + substream->ffile = NULL; + snd_pcm_oss_release_substream(substream); + snd_pcm_release_substream(substream); + } + kfree(pcm_oss_file); + return 0; +} + +static int snd_pcm_oss_open_file(struct file *file, + snd_pcm_t *pcm, + snd_pcm_oss_file_t **rpcm_oss_file, + int minor, + snd_pcm_oss_setup_t *psetup, + snd_pcm_oss_setup_t *csetup) +{ + int err = 0; + snd_pcm_oss_file_t *pcm_oss_file; + snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL; + unsigned int f_mode = file->f_mode; + + snd_assert(rpcm_oss_file != NULL, return -EINVAL); + *rpcm_oss_file = NULL; + + pcm_oss_file = kcalloc(1, sizeof(*pcm_oss_file), GFP_KERNEL); + if (pcm_oss_file == NULL) + return -ENOMEM; + + if ((f_mode & (FMODE_WRITE|FMODE_READ)) == (FMODE_WRITE|FMODE_READ) && + (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX)) + f_mode = FMODE_WRITE; + if ((f_mode & FMODE_WRITE) && !(psetup && psetup->disable)) { + if ((err = snd_pcm_open_substream(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &psubstream)) < 0) { + snd_pcm_oss_release_file(pcm_oss_file); + return err; + } + pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK] = psubstream; + } + if ((f_mode & FMODE_READ) && !(csetup && csetup->disable)) { + if ((err = snd_pcm_open_substream(pcm, SNDRV_PCM_STREAM_CAPTURE, + &csubstream)) < 0) { + if (!(f_mode & FMODE_WRITE) || err != -ENODEV) { + snd_pcm_oss_release_file(pcm_oss_file); + return err; + } else { + csubstream = NULL; + } + } + pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE] = csubstream; + } + + if (psubstream == NULL && csubstream == NULL) { + snd_pcm_oss_release_file(pcm_oss_file); + return -EINVAL; + } + if (psubstream != NULL) { + psubstream->oss.file = pcm_oss_file; + err = snd_pcm_hw_constraints_init(psubstream); + if (err < 0) { + snd_printd("snd_pcm_hw_constraint_init failed\n"); + snd_pcm_oss_release_file(pcm_oss_file); + return err; + } + if ((err = psubstream->ops->open(psubstream)) < 0) { + snd_pcm_oss_release_file(pcm_oss_file); + return err; + } + psubstream->open_flag = 1; + err = snd_pcm_hw_constraints_complete(psubstream); + if (err < 0) { + snd_printd("snd_pcm_hw_constraint_complete failed\n"); + snd_pcm_oss_release_file(pcm_oss_file); + return err; + } + psubstream->ffile = file; + snd_pcm_oss_init_substream(psubstream, psetup, minor); + } + if (csubstream != NULL) { + csubstream->oss.file = pcm_oss_file; + err = snd_pcm_hw_constraints_init(csubstream); + if (err < 0) { + snd_printd("snd_pcm_hw_constraint_init failed\n"); + snd_pcm_oss_release_file(pcm_oss_file); + return err; + } + if ((err = csubstream->ops->open(csubstream)) < 0) { + snd_pcm_oss_release_file(pcm_oss_file); + return err; + } + csubstream->open_flag = 1; + err = snd_pcm_hw_constraints_complete(csubstream); + if (err < 0) { + snd_printd("snd_pcm_hw_constraint_complete failed\n"); + snd_pcm_oss_release_file(pcm_oss_file); + return err; + } + csubstream->ffile = file; + snd_pcm_oss_init_substream(csubstream, csetup, minor); + } + + file->private_data = pcm_oss_file; + *rpcm_oss_file = pcm_oss_file; + return 0; +} + + +static int snd_pcm_oss_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + int cardnum = SNDRV_MINOR_OSS_CARD(minor); + int device; + int err; + char task_name[32]; + snd_pcm_t *pcm; + snd_pcm_oss_file_t *pcm_oss_file; + snd_pcm_oss_setup_t *psetup = NULL, *csetup = NULL; + int nonblock; + wait_queue_t wait; + + snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO); + device = SNDRV_MINOR_OSS_DEVICE(minor) == SNDRV_MINOR_OSS_PCM1 ? + adsp_map[cardnum] : dsp_map[cardnum]; + + pcm = snd_pcm_devices[(cardnum * SNDRV_PCM_DEVICES) + device]; + if (pcm == NULL) { + err = -ENODEV; + goto __error1; + } + err = snd_card_file_add(pcm->card, file); + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { + err = -EFAULT; + goto __error2; + } + if (snd_task_name(current, task_name, sizeof(task_name)) < 0) { + err = -EFAULT; + goto __error; + } + if (file->f_mode & FMODE_WRITE) + psetup = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK, task_name); + if (file->f_mode & FMODE_READ) + csetup = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE, task_name); + + nonblock = !!(file->f_flags & O_NONBLOCK); + if (psetup && !psetup->disable) { + if (psetup->nonblock) + nonblock = 1; + else if (psetup->block) + nonblock = 0; + } else if (csetup && !csetup->disable) { + if (csetup->nonblock) + nonblock = 1; + else if (csetup->block) + nonblock = 0; + } + if (!nonblock) + nonblock = nonblock_open; + + init_waitqueue_entry(&wait, current); + add_wait_queue(&pcm->open_wait, &wait); + down(&pcm->open_mutex); + while (1) { + err = snd_pcm_oss_open_file(file, pcm, &pcm_oss_file, + minor, psetup, csetup); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (nonblock) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + up(&pcm->open_mutex); + schedule(); + down(&pcm->open_mutex); + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&pcm->open_wait, &wait); + up(&pcm->open_mutex); + if (err < 0) + goto __error; + return err; + + __error: + module_put(pcm->card->module); + __error2: + snd_card_file_remove(pcm->card, file); + __error1: + return err; +} + +static int snd_pcm_oss_release(struct inode *inode, struct file *file) +{ + snd_pcm_t *pcm; + snd_pcm_substream_t *substream; + snd_pcm_oss_file_t *pcm_oss_file; + + pcm_oss_file = file->private_data; + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream == NULL) + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + snd_assert(substream != NULL, return -ENXIO); + pcm = substream->pcm; + snd_pcm_oss_sync(pcm_oss_file); + down(&pcm->open_mutex); + snd_pcm_oss_release_file(pcm_oss_file); + up(&pcm->open_mutex); + wake_up(&pcm->open_wait); + module_put(pcm->card->module); + snd_card_file_remove(pcm->card, file); + return 0; +} + +static long snd_pcm_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_pcm_oss_file_t *pcm_oss_file; + int __user *p = (int __user *)arg; + int res; + + pcm_oss_file = file->private_data; + if (cmd == OSS_GETVERSION) + return put_user(SNDRV_OSS_VERSION, p); + if (cmd == OSS_ALSAEMULVER) + return put_user(1, p); +#if defined(CONFIG_SND_MIXER_OSS) || (defined(MODULE) && defined(CONFIG_SND_MIXER_OSS_MODULE)) + if (((cmd >> 8) & 0xff) == 'M') { /* mixer ioctl - for OSS compatibility */ + snd_pcm_substream_t *substream; + int idx; + for (idx = 0; idx < 2; ++idx) { + substream = pcm_oss_file->streams[idx]; + if (substream != NULL) + break; + } + snd_assert(substream != NULL, return -ENXIO); + return snd_mixer_oss_ioctl_card(substream->pcm->card, cmd, arg); + } +#endif + if (((cmd >> 8) & 0xff) != 'P') + return -EINVAL; +#ifdef OSS_DEBUG + printk("pcm_oss: ioctl = 0x%x\n", cmd); +#endif + switch (cmd) { + case SNDCTL_DSP_RESET: + return snd_pcm_oss_reset(pcm_oss_file); + case SNDCTL_DSP_SYNC: + return snd_pcm_oss_sync(pcm_oss_file); + case SNDCTL_DSP_SPEED: + if (get_user(res, p)) + return -EFAULT; + if ((res = snd_pcm_oss_set_rate(pcm_oss_file, res))<0) + return res; + return put_user(res, p); + case SOUND_PCM_READ_RATE: + res = snd_pcm_oss_get_rate(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_STEREO: + if (get_user(res, p)) + return -EFAULT; + res = res > 0 ? 2 : 1; + if ((res = snd_pcm_oss_set_channels(pcm_oss_file, res)) < 0) + return res; + return put_user(--res, p); + case SNDCTL_DSP_GETBLKSIZE: + res = snd_pcm_oss_get_block_size(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_SETFMT: + if (get_user(res, p)) + return -EFAULT; + res = snd_pcm_oss_set_format(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(res, p); + case SOUND_PCM_READ_BITS: + res = snd_pcm_oss_get_format(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_CHANNELS: + if (get_user(res, p)) + return -EFAULT; + res = snd_pcm_oss_set_channels(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(res, p); + case SOUND_PCM_READ_CHANNELS: + res = snd_pcm_oss_get_channels(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + return -EIO; + case SNDCTL_DSP_POST: + return snd_pcm_oss_post(pcm_oss_file); + case SNDCTL_DSP_SUBDIVIDE: + if (get_user(res, p)) + return -EFAULT; + res = snd_pcm_oss_set_subdivide(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(res, p)) + return -EFAULT; + return snd_pcm_oss_set_fragment(pcm_oss_file, res); + case SNDCTL_DSP_GETFMTS: + res = snd_pcm_oss_get_formats(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETISPACE: + return snd_pcm_oss_get_space(pcm_oss_file, + cmd == SNDCTL_DSP_GETISPACE ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, + (struct audio_buf_info __user *) arg); + case SNDCTL_DSP_NONBLOCK: + return snd_pcm_oss_nonblock(file); + case SNDCTL_DSP_GETCAPS: + res = snd_pcm_oss_get_caps(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_GETTRIGGER: + res = snd_pcm_oss_get_trigger(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_SETTRIGGER: + if (get_user(res, p)) + return -EFAULT; + return snd_pcm_oss_set_trigger(pcm_oss_file, res); + case SNDCTL_DSP_GETIPTR: + case SNDCTL_DSP_GETOPTR: + return snd_pcm_oss_get_ptr(pcm_oss_file, + cmd == SNDCTL_DSP_GETIPTR ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, + (struct count_info __user *) arg); + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + return snd_pcm_oss_get_mapbuf(pcm_oss_file, + cmd == SNDCTL_DSP_MAPINBUF ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, + (struct buffmem_desc __user *) arg); + case SNDCTL_DSP_SETSYNCRO: + /* stop DMA now.. */ + return 0; + case SNDCTL_DSP_SETDUPLEX: + if (snd_pcm_oss_get_caps(pcm_oss_file) & DSP_CAP_DUPLEX) + return 0; + return -EIO; + case SNDCTL_DSP_GETODELAY: + res = snd_pcm_oss_get_odelay(pcm_oss_file); + if (res < 0) { + /* it's for sure, some broken apps don't check for error codes */ + put_user(0, p); + return res; + } + return put_user(res, p); + case SNDCTL_DSP_PROFILE: + return 0; /* silently ignore */ + default: + snd_printd("pcm_oss: unknown command = 0x%x\n", cmd); + } + return -EINVAL; +} + +#ifdef CONFIG_COMPAT +/* all compatible */ +#define snd_pcm_oss_ioctl_compat snd_pcm_oss_ioctl +#else +#define snd_pcm_oss_ioctl_compat NULL +#endif + +static ssize_t snd_pcm_oss_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + snd_pcm_oss_file_t *pcm_oss_file; + snd_pcm_substream_t *substream; + + pcm_oss_file = file->private_data; + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (substream == NULL) + return -ENXIO; +#ifndef OSS_DEBUG + return snd_pcm_oss_read1(substream, buf, count); +#else + { + ssize_t res = snd_pcm_oss_read1(substream, buf, count); + printk("pcm_oss: read %li bytes (returned %li bytes)\n", (long)count, (long)res); + return res; + } +#endif +} + +static ssize_t snd_pcm_oss_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ + snd_pcm_oss_file_t *pcm_oss_file; + snd_pcm_substream_t *substream; + long result; + + pcm_oss_file = file->private_data; + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream == NULL) + return -ENXIO; + up(&file->f_dentry->d_inode->i_sem); + result = snd_pcm_oss_write1(substream, buf, count); + down(&file->f_dentry->d_inode->i_sem); +#ifdef OSS_DEBUG + printk("pcm_oss: write %li bytes (wrote %li bytes)\n", (long)count, (long)result); +#endif + return result; +} + +static int snd_pcm_oss_playback_ready(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (atomic_read(&runtime->mmap_count)) + return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt; + else + return snd_pcm_playback_avail(runtime) >= runtime->oss.period_frames; +} + +static int snd_pcm_oss_capture_ready(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (atomic_read(&runtime->mmap_count)) + return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt; + else + return snd_pcm_capture_avail(runtime) >= runtime->oss.period_frames; +} + +static unsigned int snd_pcm_oss_poll(struct file *file, poll_table * wait) +{ + snd_pcm_oss_file_t *pcm_oss_file; + unsigned int mask; + snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL; + + pcm_oss_file = file->private_data; + + psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + + mask = 0; + if (psubstream != NULL) { + snd_pcm_runtime_t *runtime = psubstream->runtime; + poll_wait(file, &runtime->sleep, wait); + snd_pcm_stream_lock_irq(psubstream); + if (runtime->status->state != SNDRV_PCM_STATE_DRAINING && + (runtime->status->state != SNDRV_PCM_STATE_RUNNING || + snd_pcm_oss_playback_ready(psubstream))) + mask |= POLLOUT | POLLWRNORM; + snd_pcm_stream_unlock_irq(psubstream); + } + if (csubstream != NULL) { + snd_pcm_runtime_t *runtime = csubstream->runtime; + enum sndrv_pcm_state ostate; + poll_wait(file, &runtime->sleep, wait); + snd_pcm_stream_lock_irq(csubstream); + if ((ostate = runtime->status->state) != SNDRV_PCM_STATE_RUNNING || + snd_pcm_oss_capture_ready(csubstream)) + mask |= POLLIN | POLLRDNORM; + snd_pcm_stream_unlock_irq(csubstream); + if (ostate != SNDRV_PCM_STATE_RUNNING && runtime->oss.trigger) { + snd_pcm_oss_file_t ofile; + memset(&ofile, 0, sizeof(ofile)); + ofile.streams[SNDRV_PCM_STREAM_CAPTURE] = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + runtime->oss.trigger = 0; + snd_pcm_oss_set_trigger(&ofile, PCM_ENABLE_INPUT); + } + } + + return mask; +} + +static int snd_pcm_oss_mmap(struct file *file, struct vm_area_struct *area) +{ + snd_pcm_oss_file_t *pcm_oss_file; + snd_pcm_substream_t *substream = NULL; + snd_pcm_runtime_t *runtime; + int err; + +#ifdef OSS_DEBUG + printk("pcm_oss: mmap begin\n"); +#endif + pcm_oss_file = file->private_data; + switch ((area->vm_flags & (VM_READ | VM_WRITE))) { + case VM_READ | VM_WRITE: + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream) + break; + /* Fall through */ + case VM_READ: + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + break; + case VM_WRITE: + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + break; + default: + return -EINVAL; + } + /* set VM_READ access as well to fix memset() routines that do + reads before writes (to improve performance) */ + area->vm_flags |= VM_READ; + if (substream == NULL) + return -ENXIO; + runtime = substream->runtime; + if (!(runtime->info & SNDRV_PCM_INFO_MMAP_VALID)) + return -EIO; + if (runtime->info & SNDRV_PCM_INFO_INTERLEAVED) + runtime->access = SNDRV_PCM_ACCESS_MMAP_INTERLEAVED; + else + return -EIO; + + if (runtime->oss.params) { + if ((err = snd_pcm_oss_change_params(substream)) < 0) + return err; + } + if (runtime->oss.plugin_first != NULL) + return -EIO; + + if (area->vm_pgoff != 0) + return -EINVAL; + + err = snd_pcm_mmap_data(substream, file, area); + if (err < 0) + return err; + runtime->oss.mmap_bytes = area->vm_end - area->vm_start; + runtime->silence_threshold = 0; + runtime->silence_size = 0; +#ifdef OSS_DEBUG + printk("pcm_oss: mmap ok, bytes = 0x%x\n", runtime->oss.mmap_bytes); +#endif + /* In mmap mode we never stop */ + runtime->stop_threshold = runtime->boundary; + + return 0; +} + +/* + * /proc interface + */ + +static void snd_pcm_oss_proc_read(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data; + snd_pcm_oss_setup_t *setup = pstr->oss.setup_list; + down(&pstr->oss.setup_mutex); + while (setup) { + snd_iprintf(buffer, "%s %u %u%s%s%s%s%s%s\n", + setup->task_name, + setup->periods, + setup->period_size, + setup->disable ? " disable" : "", + setup->direct ? " direct" : "", + setup->block ? " block" : "", + setup->nonblock ? " non-block" : "", + setup->partialfrag ? " partial-frag" : "", + setup->nosilence ? " no-silence" : ""); + setup = setup->next; + } + up(&pstr->oss.setup_mutex); +} + +static void snd_pcm_oss_proc_free_setup_list(snd_pcm_str_t * pstr) +{ + unsigned int idx; + snd_pcm_substream_t *substream; + snd_pcm_oss_setup_t *setup, *setupn; + + for (idx = 0, substream = pstr->substream; + idx < pstr->substream_count; idx++, substream = substream->next) + substream->oss.setup = NULL; + for (setup = pstr->oss.setup_list, pstr->oss.setup_list = NULL; + setup; setup = setupn) { + setupn = setup->next; + kfree(setup->task_name); + kfree(setup); + } + pstr->oss.setup_list = NULL; +} + +static void snd_pcm_oss_proc_write(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data; + char line[128], str[32], task_name[32], *ptr; + int idx1; + snd_pcm_oss_setup_t *setup, *setup1, template; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + down(&pstr->oss.setup_mutex); + memset(&template, 0, sizeof(template)); + ptr = snd_info_get_str(task_name, line, sizeof(task_name)); + if (!strcmp(task_name, "clear") || !strcmp(task_name, "erase")) { + snd_pcm_oss_proc_free_setup_list(pstr); + up(&pstr->oss.setup_mutex); + continue; + } + for (setup = pstr->oss.setup_list; setup; setup = setup->next) { + if (!strcmp(setup->task_name, task_name)) { + template = *setup; + break; + } + } + ptr = snd_info_get_str(str, ptr, sizeof(str)); + template.periods = simple_strtoul(str, NULL, 10); + ptr = snd_info_get_str(str, ptr, sizeof(str)); + template.period_size = simple_strtoul(str, NULL, 10); + for (idx1 = 31; idx1 >= 0; idx1--) + if (template.period_size & (1 << idx1)) + break; + for (idx1--; idx1 >= 0; idx1--) + template.period_size &= ~(1 << idx1); + do { + ptr = snd_info_get_str(str, ptr, sizeof(str)); + if (!strcmp(str, "disable")) { + template.disable = 1; + } else if (!strcmp(str, "direct")) { + template.direct = 1; + } else if (!strcmp(str, "block")) { + template.block = 1; + } else if (!strcmp(str, "non-block")) { + template.nonblock = 1; + } else if (!strcmp(str, "partial-frag")) { + template.partialfrag = 1; + } else if (!strcmp(str, "no-silence")) { + template.nosilence = 1; + } + } while (*str); + if (setup == NULL) { + setup = (snd_pcm_oss_setup_t *) kmalloc(sizeof(snd_pcm_oss_setup_t), GFP_KERNEL); + if (setup) { + if (pstr->oss.setup_list == NULL) { + pstr->oss.setup_list = setup; + } else { + for (setup1 = pstr->oss.setup_list; setup1->next; setup1 = setup1->next); + setup1->next = setup; + } + template.task_name = snd_kmalloc_strdup(task_name, GFP_KERNEL); + } else { + buffer->error = -ENOMEM; + } + } + if (setup) + *setup = template; + up(&pstr->oss.setup_mutex); + } +} + +static void snd_pcm_oss_proc_init(snd_pcm_t *pcm) +{ + int stream; + for (stream = 0; stream < 2; ++stream) { + snd_info_entry_t *entry; + snd_pcm_str_t *pstr = &pcm->streams[stream]; + if (pstr->substream_count == 0) + continue; + if ((entry = snd_info_create_card_entry(pcm->card, "oss", pstr->proc_root)) != NULL) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->mode = S_IFREG | S_IRUGO | S_IWUSR; + entry->c.text.read_size = 8192; + entry->c.text.read = snd_pcm_oss_proc_read; + entry->c.text.write_size = 8192; + entry->c.text.write = snd_pcm_oss_proc_write; + entry->private_data = pstr; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + pstr->oss.proc_entry = entry; + } +} + +static void snd_pcm_oss_proc_done(snd_pcm_t *pcm) +{ + int stream; + for (stream = 0; stream < 2; ++stream) { + snd_pcm_str_t *pstr = &pcm->streams[stream]; + if (pstr->oss.proc_entry) { + snd_info_unregister(pstr->oss.proc_entry); + pstr->oss.proc_entry = NULL; + snd_pcm_oss_proc_free_setup_list(pstr); + } + } +} + +/* + * ENTRY functions + */ + +static struct file_operations snd_pcm_oss_f_reg = +{ + .owner = THIS_MODULE, + .read = snd_pcm_oss_read, + .write = snd_pcm_oss_write, + .open = snd_pcm_oss_open, + .release = snd_pcm_oss_release, + .poll = snd_pcm_oss_poll, + .unlocked_ioctl = snd_pcm_oss_ioctl, + .compat_ioctl = snd_pcm_oss_ioctl_compat, + .mmap = snd_pcm_oss_mmap, +}; + +static snd_minor_t snd_pcm_oss_reg = +{ + .comment = "digital audio", + .f_ops = &snd_pcm_oss_f_reg, +}; + +static void register_oss_dsp(snd_pcm_t *pcm, int index) +{ + char name[128]; + sprintf(name, "dsp%i%i", pcm->card->number, pcm->device); + if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, + pcm->card, index, &snd_pcm_oss_reg, + name) < 0) { + snd_printk("unable to register OSS PCM device %i:%i\n", pcm->card->number, pcm->device); + } +} + +static int snd_pcm_oss_register_minor(snd_pcm_t * pcm) +{ + pcm->oss.reg = 0; + if (dsp_map[pcm->card->number] == (int)pcm->device) { + char name[128]; + int duplex; + register_oss_dsp(pcm, 0); + duplex = (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count > 0 && + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count && + !(pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX)); + sprintf(name, "%s%s", pcm->name, duplex ? " (DUPLEX)" : ""); +#ifdef SNDRV_OSS_INFO_DEV_AUDIO + snd_oss_info_register(SNDRV_OSS_INFO_DEV_AUDIO, + pcm->card->number, + name); +#endif + pcm->oss.reg++; + pcm->oss.reg_mask |= 1; + } + if (adsp_map[pcm->card->number] == (int)pcm->device) { + register_oss_dsp(pcm, 1); + pcm->oss.reg++; + pcm->oss.reg_mask |= 2; + } + + if (pcm->oss.reg) + snd_pcm_oss_proc_init(pcm); + + return 0; +} + +static int snd_pcm_oss_disconnect_minor(snd_pcm_t * pcm) +{ + if (pcm->oss.reg) { + if (pcm->oss.reg_mask & 1) { + pcm->oss.reg_mask &= ~1; + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, + pcm->card, 0); + } + if (pcm->oss.reg_mask & 2) { + pcm->oss.reg_mask &= ~2; + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, + pcm->card, 1); + } + } + return 0; +} + +static int snd_pcm_oss_unregister_minor(snd_pcm_t * pcm) +{ + snd_pcm_oss_disconnect_minor(pcm); + if (pcm->oss.reg) { + if (dsp_map[pcm->card->number] == (int)pcm->device) { +#ifdef SNDRV_OSS_INFO_DEV_AUDIO + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_AUDIO, pcm->card->number); +#endif + } + pcm->oss.reg = 0; + snd_pcm_oss_proc_done(pcm); + } + return 0; +} + +static snd_pcm_notify_t snd_pcm_oss_notify = +{ + .n_register = snd_pcm_oss_register_minor, + .n_disconnect = snd_pcm_oss_disconnect_minor, + .n_unregister = snd_pcm_oss_unregister_minor, +}; + +static int __init alsa_pcm_oss_init(void) +{ + int i; + int err; + + /* check device map table */ + for (i = 0; i < SNDRV_CARDS; i++) { + if (dsp_map[i] < 0 || dsp_map[i] >= SNDRV_PCM_DEVICES) { + snd_printk("invalid dsp_map[%d] = %d\n", i, dsp_map[i]); + dsp_map[i] = 0; + } + if (adsp_map[i] < 0 || adsp_map[i] >= SNDRV_PCM_DEVICES) { + snd_printk("invalid adsp_map[%d] = %d\n", i, adsp_map[i]); + adsp_map[i] = 1; + } + } + if ((err = snd_pcm_notify(&snd_pcm_oss_notify, 0)) < 0) + return err; + return 0; +} + +static void __exit alsa_pcm_oss_exit(void) +{ + snd_pcm_notify(&snd_pcm_oss_notify, 1); +} + +module_init(alsa_pcm_oss_init) +module_exit(alsa_pcm_oss_exit) diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c new file mode 100644 index 000000000000..6bb31009f0b4 --- /dev/null +++ b/sound/core/oss/pcm_plugin.c @@ -0,0 +1,921 @@ +/* + * PCM Plug-In shared (kernel/library) code + * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#if 0 +#define PLUGIN_DEBUG +#endif + +#include <sound/driver.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/vmalloc.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "pcm_plugin.h" + +#define snd_pcm_plug_first(plug) ((plug)->runtime->oss.plugin_first) +#define snd_pcm_plug_last(plug) ((plug)->runtime->oss.plugin_last) + +static int snd_pcm_plugin_src_channels_mask(snd_pcm_plugin_t *plugin, + bitset_t *dst_vmask, + bitset_t **src_vmask) +{ + bitset_t *vmask = plugin->src_vmask; + bitset_copy(vmask, dst_vmask, plugin->src_format.channels); + *src_vmask = vmask; + return 0; +} + +static int snd_pcm_plugin_dst_channels_mask(snd_pcm_plugin_t *plugin, + bitset_t *src_vmask, + bitset_t **dst_vmask) +{ + bitset_t *vmask = plugin->dst_vmask; + bitset_copy(vmask, src_vmask, plugin->dst_format.channels); + *dst_vmask = vmask; + return 0; +} + +/* + * because some cards might have rates "very close", we ignore + * all "resampling" requests within +-5% + */ +static int rate_match(unsigned int src_rate, unsigned int dst_rate) +{ + unsigned int low = (src_rate * 95) / 100; + unsigned int high = (src_rate * 105) / 100; + return dst_rate >= low && dst_rate <= high; +} + +static int snd_pcm_plugin_alloc(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames) +{ + snd_pcm_plugin_format_t *format; + ssize_t width; + size_t size; + unsigned int channel; + snd_pcm_plugin_channel_t *c; + + if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) { + format = &plugin->src_format; + } else { + format = &plugin->dst_format; + } + if ((width = snd_pcm_format_physical_width(format->format)) < 0) + return width; + size = frames * format->channels * width; + snd_assert((size % 8) == 0, return -ENXIO); + size /= 8; + if (plugin->buf_frames < frames) { + vfree(plugin->buf); + plugin->buf = vmalloc(size); + plugin->buf_frames = frames; + } + if (!plugin->buf) { + plugin->buf_frames = 0; + return -ENOMEM; + } + c = plugin->buf_channels; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + for (channel = 0; channel < format->channels; channel++, c++) { + c->frames = frames; + c->enabled = 1; + c->wanted = 0; + c->area.addr = plugin->buf; + c->area.first = channel * width; + c->area.step = format->channels * width; + } + } else if (plugin->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) { + snd_assert((size % format->channels) == 0,); + size /= format->channels; + for (channel = 0; channel < format->channels; channel++, c++) { + c->frames = frames; + c->enabled = 1; + c->wanted = 0; + c->area.addr = plugin->buf + (channel * size); + c->area.first = 0; + c->area.step = width; + } + } else + return -EINVAL; + return 0; +} + +int snd_pcm_plug_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t frames) +{ + int err; + snd_assert(snd_pcm_plug_first(plug) != NULL, return -ENXIO); + if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) { + snd_pcm_plugin_t *plugin = snd_pcm_plug_first(plug); + while (plugin->next) { + if (plugin->dst_frames) + frames = plugin->dst_frames(plugin, frames); + snd_assert(frames > 0, return -ENXIO); + plugin = plugin->next; + err = snd_pcm_plugin_alloc(plugin, frames); + if (err < 0) + return err; + } + } else { + snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug); + while (plugin->prev) { + if (plugin->src_frames) + frames = plugin->src_frames(plugin, frames); + snd_assert(frames > 0, return -ENXIO); + plugin = plugin->prev; + err = snd_pcm_plugin_alloc(plugin, frames); + if (err < 0) + return err; + } + } + return 0; +} + + +snd_pcm_sframes_t snd_pcm_plugin_client_channels(snd_pcm_plugin_t *plugin, + snd_pcm_uframes_t frames, + snd_pcm_plugin_channel_t **channels) +{ + *channels = plugin->buf_channels; + return frames; +} + +int snd_pcm_plugin_build(snd_pcm_plug_t *plug, + const char *name, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + size_t extra, + snd_pcm_plugin_t **ret) +{ + snd_pcm_plugin_t *plugin; + unsigned int channels; + + snd_assert(plug != NULL, return -ENXIO); + snd_assert(src_format != NULL && dst_format != NULL, return -ENXIO); + plugin = kcalloc(1, sizeof(*plugin) + extra, GFP_KERNEL); + if (plugin == NULL) + return -ENOMEM; + plugin->name = name; + plugin->plug = plug; + plugin->stream = snd_pcm_plug_stream(plug); + plugin->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + plugin->src_format = *src_format; + plugin->src_width = snd_pcm_format_physical_width(src_format->format); + snd_assert(plugin->src_width > 0, ); + plugin->dst_format = *dst_format; + plugin->dst_width = snd_pcm_format_physical_width(dst_format->format); + snd_assert(plugin->dst_width > 0, ); + if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) + channels = src_format->channels; + else + channels = dst_format->channels; + plugin->buf_channels = kcalloc(channels, sizeof(*plugin->buf_channels), GFP_KERNEL); + if (plugin->buf_channels == NULL) { + snd_pcm_plugin_free(plugin); + return -ENOMEM; + } + plugin->src_vmask = bitset_alloc(src_format->channels); + if (plugin->src_vmask == NULL) { + snd_pcm_plugin_free(plugin); + return -ENOMEM; + } + plugin->dst_vmask = bitset_alloc(dst_format->channels); + if (plugin->dst_vmask == NULL) { + snd_pcm_plugin_free(plugin); + return -ENOMEM; + } + plugin->client_channels = snd_pcm_plugin_client_channels; + plugin->src_channels_mask = snd_pcm_plugin_src_channels_mask; + plugin->dst_channels_mask = snd_pcm_plugin_dst_channels_mask; + *ret = plugin; + return 0; +} + +int snd_pcm_plugin_free(snd_pcm_plugin_t *plugin) +{ + if (! plugin) + return 0; + if (plugin->private_free) + plugin->private_free(plugin); + kfree(plugin->buf_channels); + vfree(plugin->buf); + kfree(plugin->src_vmask); + kfree(plugin->dst_vmask); + kfree(plugin); + return 0; +} + +snd_pcm_sframes_t snd_pcm_plug_client_size(snd_pcm_plug_t *plug, snd_pcm_uframes_t drv_frames) +{ + snd_pcm_plugin_t *plugin, *plugin_prev, *plugin_next; + int stream = snd_pcm_plug_stream(plug); + + snd_assert(plug != NULL, return -ENXIO); + if (drv_frames == 0) + return 0; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + plugin = snd_pcm_plug_last(plug); + while (plugin && drv_frames > 0) { + plugin_prev = plugin->prev; + if (plugin->src_frames) + drv_frames = plugin->src_frames(plugin, drv_frames); + plugin = plugin_prev; + } + } else if (stream == SNDRV_PCM_STREAM_CAPTURE) { + plugin = snd_pcm_plug_first(plug); + while (plugin && drv_frames > 0) { + plugin_next = plugin->next; + if (plugin->dst_frames) + drv_frames = plugin->dst_frames(plugin, drv_frames); + plugin = plugin_next; + } + } else + snd_BUG(); + return drv_frames; +} + +snd_pcm_sframes_t snd_pcm_plug_slave_size(snd_pcm_plug_t *plug, snd_pcm_uframes_t clt_frames) +{ + snd_pcm_plugin_t *plugin, *plugin_prev, *plugin_next; + snd_pcm_sframes_t frames; + int stream = snd_pcm_plug_stream(plug); + + snd_assert(plug != NULL, return -ENXIO); + if (clt_frames == 0) + return 0; + frames = clt_frames; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + plugin = snd_pcm_plug_first(plug); + while (plugin && frames > 0) { + plugin_next = plugin->next; + if (plugin->dst_frames) { + frames = plugin->dst_frames(plugin, frames); + if (frames < 0) + return frames; + } + plugin = plugin_next; + } + } else if (stream == SNDRV_PCM_STREAM_CAPTURE) { + plugin = snd_pcm_plug_last(plug); + while (plugin) { + plugin_prev = plugin->prev; + if (plugin->src_frames) { + frames = plugin->src_frames(plugin, frames); + if (frames < 0) + return frames; + } + plugin = plugin_prev; + } + } else + snd_BUG(); + return frames; +} + +static int snd_pcm_plug_formats(snd_mask_t *mask, int format) +{ + snd_mask_t formats = *mask; + u64 linfmts = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE | + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE); + snd_mask_set(&formats, SNDRV_PCM_FORMAT_MU_LAW); + + if (formats.bits[0] & (u32)linfmts) + formats.bits[0] |= (u32)linfmts; + if (formats.bits[1] & (u32)(linfmts >> 32)) + formats.bits[1] |= (u32)(linfmts >> 32); + return snd_mask_test(&formats, format); +} + +static int preferred_formats[] = { + SNDRV_PCM_FORMAT_S16_LE, + SNDRV_PCM_FORMAT_S16_BE, + SNDRV_PCM_FORMAT_U16_LE, + SNDRV_PCM_FORMAT_U16_BE, + SNDRV_PCM_FORMAT_S24_LE, + SNDRV_PCM_FORMAT_S24_BE, + SNDRV_PCM_FORMAT_U24_LE, + SNDRV_PCM_FORMAT_U24_BE, + SNDRV_PCM_FORMAT_S32_LE, + SNDRV_PCM_FORMAT_S32_BE, + SNDRV_PCM_FORMAT_U32_LE, + SNDRV_PCM_FORMAT_U32_BE, + SNDRV_PCM_FORMAT_S8, + SNDRV_PCM_FORMAT_U8 +}; + +int snd_pcm_plug_slave_format(int format, snd_mask_t *format_mask) +{ + if (snd_mask_test(format_mask, format)) + return format; + if (! snd_pcm_plug_formats(format_mask, format)) + return -EINVAL; + if (snd_pcm_format_linear(format)) { + int width = snd_pcm_format_width(format); + int unsignd = snd_pcm_format_unsigned(format); + int big = snd_pcm_format_big_endian(format); + int format1; + int wid, width1=width; + int dwidth1 = 8; + for (wid = 0; wid < 4; ++wid) { + int end, big1 = big; + for (end = 0; end < 2; ++end) { + int sgn, unsignd1 = unsignd; + for (sgn = 0; sgn < 2; ++sgn) { + format1 = snd_pcm_build_linear_format(width1, unsignd1, big1); + if (format1 >= 0 && + snd_mask_test(format_mask, format1)) + goto _found; + unsignd1 = !unsignd1; + } + big1 = !big1; + } + if (width1 == 32) { + dwidth1 = -dwidth1; + width1 = width; + } + width1 += dwidth1; + } + return -EINVAL; + _found: + return format1; + } else { + unsigned int i; + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) { + int format1 = preferred_formats[i]; + if (snd_mask_test(format_mask, format1)) + return format1; + } + default: + return -EINVAL; + } + } +} + +int snd_pcm_plug_format_plugins(snd_pcm_plug_t *plug, + snd_pcm_hw_params_t *params, + snd_pcm_hw_params_t *slave_params) +{ + snd_pcm_plugin_format_t tmpformat; + snd_pcm_plugin_format_t dstformat; + snd_pcm_plugin_format_t srcformat; + int src_access, dst_access; + snd_pcm_plugin_t *plugin = NULL; + int err; + int stream = snd_pcm_plug_stream(plug); + int slave_interleaved = (params_channels(slave_params) == 1 || + params_access(slave_params) == SNDRV_PCM_ACCESS_RW_INTERLEAVED); + + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + dstformat.format = params_format(slave_params); + dstformat.rate = params_rate(slave_params); + dstformat.channels = params_channels(slave_params); + srcformat.format = params_format(params); + srcformat.rate = params_rate(params); + srcformat.channels = params_channels(params); + src_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + dst_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED : + SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + break; + case SNDRV_PCM_STREAM_CAPTURE: + dstformat.format = params_format(params); + dstformat.rate = params_rate(params); + dstformat.channels = params_channels(params); + srcformat.format = params_format(slave_params); + srcformat.rate = params_rate(slave_params); + srcformat.channels = params_channels(slave_params); + src_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED : + SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + dst_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + break; + default: + snd_BUG(); + return -EINVAL; + } + tmpformat = srcformat; + + pdprintf("srcformat: format=%i, rate=%i, channels=%i\n", + srcformat.format, + srcformat.rate, + srcformat.channels); + pdprintf("dstformat: format=%i, rate=%i, channels=%i\n", + dstformat.format, + dstformat.rate, + dstformat.channels); + + /* Format change (linearization) */ + if ((srcformat.format != dstformat.format || + !rate_match(srcformat.rate, dstformat.rate) || + srcformat.channels != dstformat.channels) && + !snd_pcm_format_linear(srcformat.format)) { + if (snd_pcm_format_linear(dstformat.format)) + tmpformat.format = dstformat.format; + else + tmpformat.format = SNDRV_PCM_FORMAT_S16; + switch (srcformat.format) { + case SNDRV_PCM_FORMAT_MU_LAW: + err = snd_pcm_plugin_build_mulaw(plug, + &srcformat, &tmpformat, + &plugin); + break; + default: + return -EINVAL; + } + pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* channels reduction */ + if (srcformat.channels > dstformat.channels) { + int sv = srcformat.channels; + int dv = dstformat.channels; + route_ttable_entry_t *ttable = kcalloc(dv * sv, sizeof(*ttable), GFP_KERNEL); + if (ttable == NULL) + return -ENOMEM; +#if 1 + if (sv == 2 && dv == 1) { + ttable[0] = HALF; + ttable[1] = HALF; + } else +#endif + { + int v; + for (v = 0; v < dv; ++v) + ttable[v * sv + v] = FULL; + } + tmpformat.channels = dstformat.channels; + if (rate_match(srcformat.rate, dstformat.rate) && + snd_pcm_format_linear(dstformat.format)) + tmpformat.format = dstformat.format; + err = snd_pcm_plugin_build_route(plug, + &srcformat, &tmpformat, + ttable, &plugin); + kfree(ttable); + pdprintf("channels reduction: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* rate resampling */ + if (!rate_match(srcformat.rate, dstformat.rate)) { + tmpformat.rate = dstformat.rate; + if (srcformat.channels == dstformat.channels && + snd_pcm_format_linear(dstformat.format)) + tmpformat.format = dstformat.format; + err = snd_pcm_plugin_build_rate(plug, + &srcformat, &tmpformat, + &plugin); + pdprintf("rate down resampling: src=%i, dst=%i returns %i\n", srcformat.rate, tmpformat.rate, err); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* channels extension */ + if (srcformat.channels < dstformat.channels) { + int sv = srcformat.channels; + int dv = dstformat.channels; + route_ttable_entry_t *ttable = kcalloc(dv * sv, sizeof(*ttable), GFP_KERNEL); + if (ttable == NULL) + return -ENOMEM; +#if 0 + { + int v; + for (v = 0; v < sv; ++v) + ttable[v * sv + v] = FULL; + } +#else + { + /* Playback is spreaded on all channels */ + int vd, vs; + for (vd = 0, vs = 0; vd < dv; ++vd) { + ttable[vd * sv + vs] = FULL; + vs++; + if (vs == sv) + vs = 0; + } + } +#endif + tmpformat.channels = dstformat.channels; + if (snd_pcm_format_linear(dstformat.format)) + tmpformat.format = dstformat.format; + err = snd_pcm_plugin_build_route(plug, + &srcformat, &tmpformat, + ttable, &plugin); + kfree(ttable); + pdprintf("channels extension: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* format change */ + if (srcformat.format != dstformat.format) { + tmpformat.format = dstformat.format; + if (tmpformat.format == SNDRV_PCM_FORMAT_MU_LAW) { + err = snd_pcm_plugin_build_mulaw(plug, + &srcformat, &tmpformat, + &plugin); + } + else if (snd_pcm_format_linear(srcformat.format) && + snd_pcm_format_linear(tmpformat.format)) { + err = snd_pcm_plugin_build_linear(plug, + &srcformat, &tmpformat, + &plugin); + } + else + return -EINVAL; + pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* de-interleave */ + if (src_access != dst_access) { + err = snd_pcm_plugin_build_copy(plug, + &srcformat, + &tmpformat, + &plugin); + pdprintf("interleave change (copy: returns %i)\n", err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + } + + return 0; +} + +snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(snd_pcm_plug_t *plug, + char *buf, + snd_pcm_uframes_t count, + snd_pcm_plugin_channel_t **channels) +{ + snd_pcm_plugin_t *plugin; + snd_pcm_plugin_channel_t *v; + snd_pcm_plugin_format_t *format; + int width, nchannels, channel; + int stream = snd_pcm_plug_stream(plug); + + snd_assert(buf != NULL, return -ENXIO); + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + plugin = snd_pcm_plug_first(plug); + format = &plugin->src_format; + } else { + plugin = snd_pcm_plug_last(plug); + format = &plugin->dst_format; + } + v = plugin->buf_channels; + *channels = v; + if ((width = snd_pcm_format_physical_width(format->format)) < 0) + return width; + nchannels = format->channels; + snd_assert(plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || format->channels <= 1, return -ENXIO); + for (channel = 0; channel < nchannels; channel++, v++) { + v->frames = count; + v->enabled = 1; + v->wanted = (stream == SNDRV_PCM_STREAM_CAPTURE); + v->area.addr = buf; + v->area.first = channel * width; + v->area.step = nchannels * width; + } + return count; +} + +static int snd_pcm_plug_playback_channels_mask(snd_pcm_plug_t *plug, + bitset_t *client_vmask) +{ + snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug); + if (plugin == NULL) { + return 0; + } else { + int schannels = plugin->dst_format.channels; + bitset_t bs[bitset_size(schannels)]; + bitset_t *srcmask; + bitset_t *dstmask = bs; + int err; + bitset_one(dstmask, schannels); + if (plugin == NULL) { + bitset_and(client_vmask, dstmask, schannels); + return 0; + } + while (1) { + err = plugin->src_channels_mask(plugin, dstmask, &srcmask); + if (err < 0) + return err; + dstmask = srcmask; + if (plugin->prev == NULL) + break; + plugin = plugin->prev; + } + bitset_and(client_vmask, dstmask, plugin->src_format.channels); + return 0; + } +} + +static int snd_pcm_plug_playback_disable_useless_channels(snd_pcm_plug_t *plug, + snd_pcm_plugin_channel_t *src_channels) +{ + snd_pcm_plugin_t *plugin = snd_pcm_plug_first(plug); + unsigned int nchannels = plugin->src_format.channels; + bitset_t bs[bitset_size(nchannels)]; + bitset_t *srcmask = bs; + int err; + unsigned int channel; + for (channel = 0; channel < nchannels; channel++) { + if (src_channels[channel].enabled) + bitset_set(srcmask, channel); + else + bitset_reset(srcmask, channel); + } + err = snd_pcm_plug_playback_channels_mask(plug, srcmask); + if (err < 0) + return err; + for (channel = 0; channel < nchannels; channel++) { + if (!bitset_get(srcmask, channel)) + src_channels[channel].enabled = 0; + } + return 0; +} + +static int snd_pcm_plug_capture_disable_useless_channels(snd_pcm_plug_t *plug, + snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *client_channels) +{ + snd_pcm_plugin_t *plugin = snd_pcm_plug_last(plug); + unsigned int nchannels = plugin->dst_format.channels; + bitset_t bs[bitset_size(nchannels)]; + bitset_t *dstmask = bs; + bitset_t *srcmask; + int err; + unsigned int channel; + for (channel = 0; channel < nchannels; channel++) { + if (client_channels[channel].enabled) + bitset_set(dstmask, channel); + else + bitset_reset(dstmask, channel); + } + while (plugin) { + err = plugin->src_channels_mask(plugin, dstmask, &srcmask); + if (err < 0) + return err; + dstmask = srcmask; + plugin = plugin->prev; + } + plugin = snd_pcm_plug_first(plug); + nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; channel++) { + if (!bitset_get(dstmask, channel)) + src_channels[channel].enabled = 0; + } + return 0; +} + +snd_pcm_sframes_t snd_pcm_plug_write_transfer(snd_pcm_plug_t *plug, snd_pcm_plugin_channel_t *src_channels, snd_pcm_uframes_t size) +{ + snd_pcm_plugin_t *plugin, *next; + snd_pcm_plugin_channel_t *dst_channels; + int err; + snd_pcm_sframes_t frames = size; + + if ((err = snd_pcm_plug_playback_disable_useless_channels(plug, src_channels)) < 0) + return err; + + plugin = snd_pcm_plug_first(plug); + while (plugin && frames > 0) { + if ((next = plugin->next) != NULL) { + snd_pcm_sframes_t frames1 = frames; + if (plugin->dst_frames) + frames1 = plugin->dst_frames(plugin, frames); + if ((err = next->client_channels(next, frames1, &dst_channels)) < 0) { + return err; + } + if (err != frames1) { + frames = err; + if (plugin->src_frames) + frames = plugin->src_frames(plugin, frames1); + } + } else + dst_channels = NULL; + pdprintf("write plugin: %s, %li\n", plugin->name, frames); + if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0) + return frames; + src_channels = dst_channels; + plugin = next; + } + return snd_pcm_plug_client_size(plug, frames); +} + +snd_pcm_sframes_t snd_pcm_plug_read_transfer(snd_pcm_plug_t *plug, snd_pcm_plugin_channel_t *dst_channels_final, snd_pcm_uframes_t size) +{ + snd_pcm_plugin_t *plugin, *next; + snd_pcm_plugin_channel_t *src_channels, *dst_channels; + snd_pcm_sframes_t frames = size; + int err; + + frames = snd_pcm_plug_slave_size(plug, frames); + if (frames < 0) + return frames; + + src_channels = NULL; + plugin = snd_pcm_plug_first(plug); + while (plugin && frames > 0) { + if ((next = plugin->next) != NULL) { + if ((err = plugin->client_channels(plugin, frames, &dst_channels)) < 0) { + return err; + } + frames = err; + if (!plugin->prev) { + if ((err = snd_pcm_plug_capture_disable_useless_channels(plug, dst_channels, dst_channels_final)) < 0) + return err; + } + } else { + dst_channels = dst_channels_final; + } + pdprintf("read plugin: %s, %li\n", plugin->name, frames); + if ((frames = plugin->transfer(plugin, src_channels, dst_channels, frames)) < 0) + return frames; + plugin = next; + src_channels = dst_channels; + } + return frames; +} + +int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_area, size_t dst_offset, + size_t samples, int format) +{ + /* FIXME: sub byte resolution and odd dst_offset */ + unsigned char *dst; + unsigned int dst_step; + int width; + const unsigned char *silence; + if (!dst_area->addr) + return 0; + dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8; + width = snd_pcm_format_physical_width(format); + if (width <= 0) + return -EINVAL; + if (dst_area->step == (unsigned int) width && width >= 8) + return snd_pcm_format_set_silence(format, dst, samples); + silence = snd_pcm_format_silence_64(format); + if (! silence) + return -EINVAL; + dst_step = dst_area->step / 8; + if (width == 4) { + /* Ima ADPCM */ + int dstbit = dst_area->first % 8; + int dstbit_step = dst_area->step % 8; + while (samples-- > 0) { + if (dstbit) + *dst &= 0xf0; + else + *dst &= 0x0f; + dst += dst_step; + dstbit += dstbit_step; + if (dstbit == 8) { + dst++; + dstbit = 0; + } + } + } else { + width /= 8; + while (samples-- > 0) { + memcpy(dst, silence, width); + dst += dst_step; + } + } + return 0; +} + +int snd_pcm_area_copy(const snd_pcm_channel_area_t *src_area, size_t src_offset, + const snd_pcm_channel_area_t *dst_area, size_t dst_offset, + size_t samples, int format) +{ + /* FIXME: sub byte resolution and odd dst_offset */ + char *src, *dst; + int width; + int src_step, dst_step; + src = src_area->addr + (src_area->first + src_area->step * src_offset) / 8; + if (!src_area->addr) + return snd_pcm_area_silence(dst_area, dst_offset, samples, format); + dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8; + if (!dst_area->addr) + return 0; + width = snd_pcm_format_physical_width(format); + if (width <= 0) + return -EINVAL; + if (src_area->step == (unsigned int) width && + dst_area->step == (unsigned int) width && width >= 8) { + size_t bytes = samples * width / 8; + memcpy(dst, src, bytes); + return 0; + } + src_step = src_area->step / 8; + dst_step = dst_area->step / 8; + if (width == 4) { + /* Ima ADPCM */ + int srcbit = src_area->first % 8; + int srcbit_step = src_area->step % 8; + int dstbit = dst_area->first % 8; + int dstbit_step = dst_area->step % 8; + while (samples-- > 0) { + unsigned char srcval; + if (srcbit) + srcval = *src & 0x0f; + else + srcval = (*src & 0xf0) >> 4; + if (dstbit) + *dst = (*dst & 0xf0) | srcval; + else + *dst = (*dst & 0x0f) | (srcval << 4); + src += src_step; + srcbit += srcbit_step; + if (srcbit == 8) { + src++; + srcbit = 0; + } + dst += dst_step; + dstbit += dstbit_step; + if (dstbit == 8) { + dst++; + dstbit = 0; + } + } + } else { + width /= 8; + while (samples-- > 0) { + memcpy(dst, src, width); + src += src_step; + dst += dst_step; + } + } + return 0; +} diff --git a/sound/core/oss/pcm_plugin.h b/sound/core/oss/pcm_plugin.h new file mode 100644 index 000000000000..0f86ce477490 --- /dev/null +++ b/sound/core/oss/pcm_plugin.h @@ -0,0 +1,250 @@ +#ifndef __PCM_PLUGIN_H +#define __PCM_PLUGIN_H + +/* + * Digital Audio (Plugin interface) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef ATTRIBUTE_UNUSED +#define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +#endif + +typedef unsigned int bitset_t; + +static inline size_t bitset_size(int nbits) +{ + return (nbits + sizeof(bitset_t) * 8 - 1) / (sizeof(bitset_t) * 8); +} + +static inline bitset_t *bitset_alloc(int nbits) +{ + return kcalloc(bitset_size(nbits), sizeof(bitset_t), GFP_KERNEL); +} + +static inline void bitset_set(bitset_t *bitmap, unsigned int pos) +{ + size_t bits = sizeof(*bitmap) * 8; + bitmap[pos / bits] |= 1 << (pos % bits); +} + +static inline void bitset_reset(bitset_t *bitmap, unsigned int pos) +{ + size_t bits = sizeof(*bitmap) * 8; + bitmap[pos / bits] &= ~(1 << (pos % bits)); +} + +static inline int bitset_get(bitset_t *bitmap, unsigned int pos) +{ + size_t bits = sizeof(*bitmap) * 8; + return !!(bitmap[pos / bits] & (1 << (pos % bits))); +} + +static inline void bitset_copy(bitset_t *dst, bitset_t *src, unsigned int nbits) +{ + memcpy(dst, src, bitset_size(nbits) * sizeof(bitset_t)); +} + +static inline void bitset_and(bitset_t *dst, bitset_t *bs, unsigned int nbits) +{ + bitset_t *end = dst + bitset_size(nbits); + while (dst < end) + *dst++ &= *bs++; +} + +static inline void bitset_or(bitset_t *dst, bitset_t *bs, unsigned int nbits) +{ + bitset_t *end = dst + bitset_size(nbits); + while (dst < end) + *dst++ |= *bs++; +} + +static inline void bitset_zero(bitset_t *dst, unsigned int nbits) +{ + bitset_t *end = dst + bitset_size(nbits); + while (dst < end) + *dst++ = 0; +} + +static inline void bitset_one(bitset_t *dst, unsigned int nbits) +{ + bitset_t *end = dst + bitset_size(nbits); + while (dst < end) + *dst++ = ~(bitset_t)0; +} + +#define snd_pcm_plug_t snd_pcm_substream_t +#define snd_pcm_plug_stream(plug) ((plug)->stream) + +typedef enum { + INIT = 0, + PREPARE = 1, +} snd_pcm_plugin_action_t; + +typedef struct _snd_pcm_channel_area { + void *addr; /* base address of channel samples */ + unsigned int first; /* offset to first sample in bits */ + unsigned int step; /* samples distance in bits */ +} snd_pcm_channel_area_t; + +typedef struct _snd_pcm_plugin_channel { + void *aptr; /* pointer to the allocated area */ + snd_pcm_channel_area_t area; + snd_pcm_uframes_t frames; /* allocated frames */ + unsigned int enabled:1; /* channel need to be processed */ + unsigned int wanted:1; /* channel is wanted */ +} snd_pcm_plugin_channel_t; + +typedef struct _snd_pcm_plugin_format { + int format; + unsigned int rate; + unsigned int channels; +} snd_pcm_plugin_format_t; + +struct _snd_pcm_plugin { + const char *name; /* plug-in name */ + int stream; + snd_pcm_plugin_format_t src_format; /* source format */ + snd_pcm_plugin_format_t dst_format; /* destination format */ + int src_width; /* sample width in bits */ + int dst_width; /* sample width in bits */ + int access; + snd_pcm_sframes_t (*src_frames)(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t dst_frames); + snd_pcm_sframes_t (*dst_frames)(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t src_frames); + snd_pcm_sframes_t (*client_channels)(snd_pcm_plugin_t *plugin, + snd_pcm_uframes_t frames, + snd_pcm_plugin_channel_t **channels); + int (*src_channels_mask)(snd_pcm_plugin_t *plugin, + bitset_t *dst_vmask, + bitset_t **src_vmask); + int (*dst_channels_mask)(snd_pcm_plugin_t *plugin, + bitset_t *src_vmask, + bitset_t **dst_vmask); + snd_pcm_sframes_t (*transfer)(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames); + int (*action)(snd_pcm_plugin_t *plugin, + snd_pcm_plugin_action_t action, + unsigned long data); + snd_pcm_plugin_t *prev; + snd_pcm_plugin_t *next; + snd_pcm_plug_t *plug; + void *private_data; + void (*private_free)(snd_pcm_plugin_t *plugin); + char *buf; + snd_pcm_uframes_t buf_frames; + snd_pcm_plugin_channel_t *buf_channels; + bitset_t *src_vmask; + bitset_t *dst_vmask; + char extra_data[0]; +}; + +int snd_pcm_plugin_build(snd_pcm_plug_t *handle, + const char *name, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + size_t extra, + snd_pcm_plugin_t **ret); +int snd_pcm_plugin_free(snd_pcm_plugin_t *plugin); +int snd_pcm_plugin_clear(snd_pcm_plugin_t **first); +int snd_pcm_plug_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t frames); +snd_pcm_sframes_t snd_pcm_plug_client_size(snd_pcm_plug_t *handle, snd_pcm_uframes_t drv_size); +snd_pcm_sframes_t snd_pcm_plug_slave_size(snd_pcm_plug_t *handle, snd_pcm_uframes_t clt_size); + +#define FULL ROUTE_PLUGIN_RESOLUTION +#define HALF ROUTE_PLUGIN_RESOLUTION / 2 +typedef int route_ttable_entry_t; + +int snd_pcm_plugin_build_io(snd_pcm_plug_t *handle, + snd_pcm_hw_params_t *params, + snd_pcm_plugin_t **r_plugin); +int snd_pcm_plugin_build_linear(snd_pcm_plug_t *handle, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + snd_pcm_plugin_t **r_plugin); +int snd_pcm_plugin_build_mulaw(snd_pcm_plug_t *handle, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + snd_pcm_plugin_t **r_plugin); +int snd_pcm_plugin_build_rate(snd_pcm_plug_t *handle, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + snd_pcm_plugin_t **r_plugin); +int snd_pcm_plugin_build_route(snd_pcm_plug_t *handle, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + route_ttable_entry_t *ttable, + snd_pcm_plugin_t **r_plugin); +int snd_pcm_plugin_build_copy(snd_pcm_plug_t *handle, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + snd_pcm_plugin_t **r_plugin); + +int snd_pcm_plug_format_plugins(snd_pcm_plug_t *substream, + snd_pcm_hw_params_t *params, + snd_pcm_hw_params_t *slave_params); + +int snd_pcm_plug_slave_format(int format, snd_mask_t *format_mask); + +int snd_pcm_plugin_append(snd_pcm_plugin_t *plugin); + +snd_pcm_sframes_t snd_pcm_plug_write_transfer(snd_pcm_plug_t *handle, snd_pcm_plugin_channel_t *src_channels, snd_pcm_uframes_t size); +snd_pcm_sframes_t snd_pcm_plug_read_transfer(snd_pcm_plug_t *handle, snd_pcm_plugin_channel_t *dst_channels_final, snd_pcm_uframes_t size); + +snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(snd_pcm_plug_t *handle, + char *buf, snd_pcm_uframes_t count, + snd_pcm_plugin_channel_t **channels); + +snd_pcm_sframes_t snd_pcm_plugin_client_channels(snd_pcm_plugin_t *plugin, + snd_pcm_uframes_t frames, + snd_pcm_plugin_channel_t **channels); + +int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_channel, size_t dst_offset, + size_t samples, int format); +int snd_pcm_area_copy(const snd_pcm_channel_area_t *src_channel, size_t src_offset, + const snd_pcm_channel_area_t *dst_channel, size_t dst_offset, + size_t samples, int format); + +void *snd_pcm_plug_buf_alloc(snd_pcm_plug_t *plug, snd_pcm_uframes_t size); +void snd_pcm_plug_buf_unlock(snd_pcm_plug_t *plug, void *ptr); +snd_pcm_sframes_t snd_pcm_oss_write3(snd_pcm_substream_t *substream, const char *ptr, snd_pcm_uframes_t size, int in_kernel); +snd_pcm_sframes_t snd_pcm_oss_read3(snd_pcm_substream_t *substream, char *ptr, snd_pcm_uframes_t size, int in_kernel); +snd_pcm_sframes_t snd_pcm_oss_writev3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel); +snd_pcm_sframes_t snd_pcm_oss_readv3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel); + + + +#define ROUTE_PLUGIN_RESOLUTION 16 + +int getput_index(int format); +int copy_index(int format); +int conv_index(int src_format, int dst_format); + +void zero_channel(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *dst_channel, + size_t samples); + +#ifdef PLUGIN_DEBUG +#define pdprintf( fmt, args... ) printk( "plugin: " fmt, ##args) +#else +#define pdprintf( fmt, args... ) +#endif + +#endif /* __PCM_PLUGIN_H */ diff --git a/sound/core/oss/plugin_ops.h b/sound/core/oss/plugin_ops.h new file mode 100644 index 000000000000..0607e9566084 --- /dev/null +++ b/sound/core/oss/plugin_ops.h @@ -0,0 +1,536 @@ +/* + * Plugin sample operators with fast switch + * Copyright (c) 2000 by Jaroslav Kysela <perex@suse.cz> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#define as_u8(ptr) (*(u_int8_t*)(ptr)) +#define as_u16(ptr) (*(u_int16_t*)(ptr)) +#define as_u32(ptr) (*(u_int32_t*)(ptr)) +#define as_u64(ptr) (*(u_int64_t*)(ptr)) +#define as_s8(ptr) (*(int8_t*)(ptr)) +#define as_s16(ptr) (*(int16_t*)(ptr)) +#define as_s32(ptr) (*(int32_t*)(ptr)) +#define as_s64(ptr) (*(int64_t*)(ptr)) + +#ifdef COPY_LABELS +static void *copy_labels[4] = { + &©_8, + &©_16, + &©_32, + &©_64 +}; +#endif + +#ifdef COPY_END +while(0) { +copy_8: as_s8(dst) = as_s8(src); goto COPY_END; +copy_16: as_s16(dst) = as_s16(src); goto COPY_END; +copy_32: as_s32(dst) = as_s32(src); goto COPY_END; +copy_64: as_s64(dst) = as_s64(src); goto COPY_END; +} +#endif + +#ifdef CONV_LABELS +/* src_wid src_endswap sign_toggle dst_wid dst_endswap */ +static void *conv_labels[4 * 2 * 2 * 4 * 2] = { + &&conv_xxx1_xxx1, /* 8h -> 8h */ + &&conv_xxx1_xxx1, /* 8h -> 8s */ + &&conv_xxx1_xx10, /* 8h -> 16h */ + &&conv_xxx1_xx01, /* 8h -> 16s */ + &&conv_xxx1_x100, /* 8h -> 24h */ + &&conv_xxx1_001x, /* 8h -> 24s */ + &&conv_xxx1_1000, /* 8h -> 32h */ + &&conv_xxx1_0001, /* 8h -> 32s */ + &&conv_xxx1_xxx9, /* 8h ^> 8h */ + &&conv_xxx1_xxx9, /* 8h ^> 8s */ + &&conv_xxx1_xx90, /* 8h ^> 16h */ + &&conv_xxx1_xx09, /* 8h ^> 16s */ + &&conv_xxx1_x900, /* 8h ^> 24h */ + &&conv_xxx1_009x, /* 8h ^> 24s */ + &&conv_xxx1_9000, /* 8h ^> 32h */ + &&conv_xxx1_0009, /* 8h ^> 32s */ + &&conv_xxx1_xxx1, /* 8s -> 8h */ + &&conv_xxx1_xxx1, /* 8s -> 8s */ + &&conv_xxx1_xx10, /* 8s -> 16h */ + &&conv_xxx1_xx01, /* 8s -> 16s */ + &&conv_xxx1_x100, /* 8s -> 24h */ + &&conv_xxx1_001x, /* 8s -> 24s */ + &&conv_xxx1_1000, /* 8s -> 32h */ + &&conv_xxx1_0001, /* 8s -> 32s */ + &&conv_xxx1_xxx9, /* 8s ^> 8h */ + &&conv_xxx1_xxx9, /* 8s ^> 8s */ + &&conv_xxx1_xx90, /* 8s ^> 16h */ + &&conv_xxx1_xx09, /* 8s ^> 16s */ + &&conv_xxx1_x900, /* 8s ^> 24h */ + &&conv_xxx1_009x, /* 8s ^> 24s */ + &&conv_xxx1_9000, /* 8s ^> 32h */ + &&conv_xxx1_0009, /* 8s ^> 32s */ + &&conv_xx12_xxx1, /* 16h -> 8h */ + &&conv_xx12_xxx1, /* 16h -> 8s */ + &&conv_xx12_xx12, /* 16h -> 16h */ + &&conv_xx12_xx21, /* 16h -> 16s */ + &&conv_xx12_x120, /* 16h -> 24h */ + &&conv_xx12_021x, /* 16h -> 24s */ + &&conv_xx12_1200, /* 16h -> 32h */ + &&conv_xx12_0021, /* 16h -> 32s */ + &&conv_xx12_xxx9, /* 16h ^> 8h */ + &&conv_xx12_xxx9, /* 16h ^> 8s */ + &&conv_xx12_xx92, /* 16h ^> 16h */ + &&conv_xx12_xx29, /* 16h ^> 16s */ + &&conv_xx12_x920, /* 16h ^> 24h */ + &&conv_xx12_029x, /* 16h ^> 24s */ + &&conv_xx12_9200, /* 16h ^> 32h */ + &&conv_xx12_0029, /* 16h ^> 32s */ + &&conv_xx12_xxx2, /* 16s -> 8h */ + &&conv_xx12_xxx2, /* 16s -> 8s */ + &&conv_xx12_xx21, /* 16s -> 16h */ + &&conv_xx12_xx12, /* 16s -> 16s */ + &&conv_xx12_x210, /* 16s -> 24h */ + &&conv_xx12_012x, /* 16s -> 24s */ + &&conv_xx12_2100, /* 16s -> 32h */ + &&conv_xx12_0012, /* 16s -> 32s */ + &&conv_xx12_xxxA, /* 16s ^> 8h */ + &&conv_xx12_xxxA, /* 16s ^> 8s */ + &&conv_xx12_xxA1, /* 16s ^> 16h */ + &&conv_xx12_xx1A, /* 16s ^> 16s */ + &&conv_xx12_xA10, /* 16s ^> 24h */ + &&conv_xx12_01Ax, /* 16s ^> 24s */ + &&conv_xx12_A100, /* 16s ^> 32h */ + &&conv_xx12_001A, /* 16s ^> 32s */ + &&conv_x123_xxx1, /* 24h -> 8h */ + &&conv_x123_xxx1, /* 24h -> 8s */ + &&conv_x123_xx12, /* 24h -> 16h */ + &&conv_x123_xx21, /* 24h -> 16s */ + &&conv_x123_x123, /* 24h -> 24h */ + &&conv_x123_321x, /* 24h -> 24s */ + &&conv_x123_1230, /* 24h -> 32h */ + &&conv_x123_0321, /* 24h -> 32s */ + &&conv_x123_xxx9, /* 24h ^> 8h */ + &&conv_x123_xxx9, /* 24h ^> 8s */ + &&conv_x123_xx92, /* 24h ^> 16h */ + &&conv_x123_xx29, /* 24h ^> 16s */ + &&conv_x123_x923, /* 24h ^> 24h */ + &&conv_x123_329x, /* 24h ^> 24s */ + &&conv_x123_9230, /* 24h ^> 32h */ + &&conv_x123_0329, /* 24h ^> 32s */ + &&conv_123x_xxx3, /* 24s -> 8h */ + &&conv_123x_xxx3, /* 24s -> 8s */ + &&conv_123x_xx32, /* 24s -> 16h */ + &&conv_123x_xx23, /* 24s -> 16s */ + &&conv_123x_x321, /* 24s -> 24h */ + &&conv_123x_123x, /* 24s -> 24s */ + &&conv_123x_3210, /* 24s -> 32h */ + &&conv_123x_0123, /* 24s -> 32s */ + &&conv_123x_xxxB, /* 24s ^> 8h */ + &&conv_123x_xxxB, /* 24s ^> 8s */ + &&conv_123x_xxB2, /* 24s ^> 16h */ + &&conv_123x_xx2B, /* 24s ^> 16s */ + &&conv_123x_xB21, /* 24s ^> 24h */ + &&conv_123x_12Bx, /* 24s ^> 24s */ + &&conv_123x_B210, /* 24s ^> 32h */ + &&conv_123x_012B, /* 24s ^> 32s */ + &&conv_1234_xxx1, /* 32h -> 8h */ + &&conv_1234_xxx1, /* 32h -> 8s */ + &&conv_1234_xx12, /* 32h -> 16h */ + &&conv_1234_xx21, /* 32h -> 16s */ + &&conv_1234_x123, /* 32h -> 24h */ + &&conv_1234_321x, /* 32h -> 24s */ + &&conv_1234_1234, /* 32h -> 32h */ + &&conv_1234_4321, /* 32h -> 32s */ + &&conv_1234_xxx9, /* 32h ^> 8h */ + &&conv_1234_xxx9, /* 32h ^> 8s */ + &&conv_1234_xx92, /* 32h ^> 16h */ + &&conv_1234_xx29, /* 32h ^> 16s */ + &&conv_1234_x923, /* 32h ^> 24h */ + &&conv_1234_329x, /* 32h ^> 24s */ + &&conv_1234_9234, /* 32h ^> 32h */ + &&conv_1234_4329, /* 32h ^> 32s */ + &&conv_1234_xxx4, /* 32s -> 8h */ + &&conv_1234_xxx4, /* 32s -> 8s */ + &&conv_1234_xx43, /* 32s -> 16h */ + &&conv_1234_xx34, /* 32s -> 16s */ + &&conv_1234_x432, /* 32s -> 24h */ + &&conv_1234_234x, /* 32s -> 24s */ + &&conv_1234_4321, /* 32s -> 32h */ + &&conv_1234_1234, /* 32s -> 32s */ + &&conv_1234_xxxC, /* 32s ^> 8h */ + &&conv_1234_xxxC, /* 32s ^> 8s */ + &&conv_1234_xxC3, /* 32s ^> 16h */ + &&conv_1234_xx3C, /* 32s ^> 16s */ + &&conv_1234_xC32, /* 32s ^> 24h */ + &&conv_1234_23Cx, /* 32s ^> 24s */ + &&conv_1234_C321, /* 32s ^> 32h */ + &&conv_1234_123C, /* 32s ^> 32s */ +}; +#endif + +#ifdef CONV_END +while(0) { +conv_xxx1_xxx1: as_u8(dst) = as_u8(src); goto CONV_END; +conv_xxx1_xx10: as_u16(dst) = (u_int16_t)as_u8(src) << 8; goto CONV_END; +conv_xxx1_xx01: as_u16(dst) = (u_int16_t)as_u8(src); goto CONV_END; +conv_xxx1_x100: as_u32(dst) = (u_int32_t)as_u8(src) << 16; goto CONV_END; +conv_xxx1_001x: as_u32(dst) = (u_int32_t)as_u8(src) << 8; goto CONV_END; +conv_xxx1_1000: as_u32(dst) = (u_int32_t)as_u8(src) << 24; goto CONV_END; +conv_xxx1_0001: as_u32(dst) = (u_int32_t)as_u8(src); goto CONV_END; +conv_xxx1_xxx9: as_u8(dst) = as_u8(src) ^ 0x80; goto CONV_END; +conv_xxx1_xx90: as_u16(dst) = (u_int16_t)(as_u8(src) ^ 0x80) << 8; goto CONV_END; +conv_xxx1_xx09: as_u16(dst) = (u_int16_t)(as_u8(src) ^ 0x80); goto CONV_END; +conv_xxx1_x900: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 16; goto CONV_END; +conv_xxx1_009x: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 8; goto CONV_END; +conv_xxx1_9000: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80) << 24; goto CONV_END; +conv_xxx1_0009: as_u32(dst) = (u_int32_t)(as_u8(src) ^ 0x80); goto CONV_END; +conv_xx12_xxx1: as_u8(dst) = as_u16(src) >> 8; goto CONV_END; +conv_xx12_xx12: as_u16(dst) = as_u16(src); goto CONV_END; +conv_xx12_xx21: as_u16(dst) = swab16(as_u16(src)); goto CONV_END; +conv_xx12_x120: as_u32(dst) = (u_int32_t)as_u16(src) << 8; goto CONV_END; +conv_xx12_021x: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 8; goto CONV_END; +conv_xx12_1200: as_u32(dst) = (u_int32_t)as_u16(src) << 16; goto CONV_END; +conv_xx12_0021: as_u32(dst) = (u_int32_t)swab16(as_u16(src)); goto CONV_END; +conv_xx12_xxx9: as_u8(dst) = (as_u16(src) >> 8) ^ 0x80; goto CONV_END; +conv_xx12_xx92: as_u16(dst) = as_u16(src) ^ 0x8000; goto CONV_END; +conv_xx12_xx29: as_u16(dst) = swab16(as_u16(src)) ^ 0x80; goto CONV_END; +conv_xx12_x920: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x8000) << 8; goto CONV_END; +conv_xx12_029x: as_u32(dst) = (u_int32_t)(swab16(as_u16(src)) ^ 0x80) << 8; goto CONV_END; +conv_xx12_9200: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x8000) << 16; goto CONV_END; +conv_xx12_0029: as_u32(dst) = (u_int32_t)(swab16(as_u16(src)) ^ 0x80); goto CONV_END; +conv_xx12_xxx2: as_u8(dst) = as_u16(src) & 0xff; goto CONV_END; +conv_xx12_x210: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 8; goto CONV_END; +conv_xx12_012x: as_u32(dst) = (u_int32_t)as_u16(src) << 8; goto CONV_END; +conv_xx12_2100: as_u32(dst) = (u_int32_t)swab16(as_u16(src)) << 16; goto CONV_END; +conv_xx12_0012: as_u32(dst) = (u_int32_t)as_u16(src); goto CONV_END; +conv_xx12_xxxA: as_u8(dst) = (as_u16(src) ^ 0x80) & 0xff; goto CONV_END; +conv_xx12_xxA1: as_u16(dst) = swab16(as_u16(src) ^ 0x80); goto CONV_END; +conv_xx12_xx1A: as_u16(dst) = as_u16(src) ^ 0x80; goto CONV_END; +conv_xx12_xA10: as_u32(dst) = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 8; goto CONV_END; +conv_xx12_01Ax: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x80) << 8; goto CONV_END; +conv_xx12_A100: as_u32(dst) = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 16; goto CONV_END; +conv_xx12_001A: as_u32(dst) = (u_int32_t)(as_u16(src) ^ 0x80); goto CONV_END; +conv_x123_xxx1: as_u8(dst) = as_u32(src) >> 16; goto CONV_END; +conv_x123_xx12: as_u16(dst) = as_u32(src) >> 8; goto CONV_END; +conv_x123_xx21: as_u16(dst) = swab16(as_u32(src) >> 8); goto CONV_END; +conv_x123_x123: as_u32(dst) = as_u32(src); goto CONV_END; +conv_x123_321x: as_u32(dst) = swab32(as_u32(src)); goto CONV_END; +conv_x123_1230: as_u32(dst) = as_u32(src) << 8; goto CONV_END; +conv_x123_0321: as_u32(dst) = swab32(as_u32(src)) >> 8; goto CONV_END; +conv_x123_xxx9: as_u8(dst) = (as_u32(src) >> 16) ^ 0x80; goto CONV_END; +conv_x123_xx92: as_u16(dst) = (as_u32(src) >> 8) ^ 0x8000; goto CONV_END; +conv_x123_xx29: as_u16(dst) = swab16(as_u32(src) >> 8) ^ 0x80; goto CONV_END; +conv_x123_x923: as_u32(dst) = as_u32(src) ^ 0x800000; goto CONV_END; +conv_x123_329x: as_u32(dst) = swab32(as_u32(src)) ^ 0x8000; goto CONV_END; +conv_x123_9230: as_u32(dst) = (as_u32(src) ^ 0x800000) << 8; goto CONV_END; +conv_x123_0329: as_u32(dst) = (swab32(as_u32(src)) >> 8) ^ 0x80; goto CONV_END; +conv_123x_xxx3: as_u8(dst) = (as_u32(src) >> 8) & 0xff; goto CONV_END; +conv_123x_xx32: as_u16(dst) = swab16(as_u32(src) >> 8); goto CONV_END; +conv_123x_xx23: as_u16(dst) = (as_u32(src) >> 8) & 0xffff; goto CONV_END; +conv_123x_x321: as_u32(dst) = swab32(as_u32(src)); goto CONV_END; +conv_123x_123x: as_u32(dst) = as_u32(src); goto CONV_END; +conv_123x_3210: as_u32(dst) = swab32(as_u32(src)) << 8; goto CONV_END; +conv_123x_0123: as_u32(dst) = as_u32(src) >> 8; goto CONV_END; +conv_123x_xxxB: as_u8(dst) = ((as_u32(src) >> 8) & 0xff) ^ 0x80; goto CONV_END; +conv_123x_xxB2: as_u16(dst) = swab16((as_u32(src) >> 8) ^ 0x80); goto CONV_END; +conv_123x_xx2B: as_u16(dst) = ((as_u32(src) >> 8) & 0xffff) ^ 0x80; goto CONV_END; +conv_123x_xB21: as_u32(dst) = swab32(as_u32(src)) ^ 0x800000; goto CONV_END; +conv_123x_12Bx: as_u32(dst) = as_u32(src) ^ 0x8000; goto CONV_END; +conv_123x_B210: as_u32(dst) = swab32(as_u32(src) ^ 0x8000) << 8; goto CONV_END; +conv_123x_012B: as_u32(dst) = (as_u32(src) >> 8) ^ 0x80; goto CONV_END; +conv_1234_xxx1: as_u8(dst) = as_u32(src) >> 24; goto CONV_END; +conv_1234_xx12: as_u16(dst) = as_u32(src) >> 16; goto CONV_END; +conv_1234_xx21: as_u16(dst) = swab16(as_u32(src) >> 16); goto CONV_END; +conv_1234_x123: as_u32(dst) = as_u32(src) >> 8; goto CONV_END; +conv_1234_321x: as_u32(dst) = swab32(as_u32(src)) << 8; goto CONV_END; +conv_1234_1234: as_u32(dst) = as_u32(src); goto CONV_END; +conv_1234_4321: as_u32(dst) = swab32(as_u32(src)); goto CONV_END; +conv_1234_xxx9: as_u8(dst) = (as_u32(src) >> 24) ^ 0x80; goto CONV_END; +conv_1234_xx92: as_u16(dst) = (as_u32(src) >> 16) ^ 0x8000; goto CONV_END; +conv_1234_xx29: as_u16(dst) = swab16(as_u32(src) >> 16) ^ 0x80; goto CONV_END; +conv_1234_x923: as_u32(dst) = (as_u32(src) >> 8) ^ 0x800000; goto CONV_END; +conv_1234_329x: as_u32(dst) = (swab32(as_u32(src)) ^ 0x80) << 8; goto CONV_END; +conv_1234_9234: as_u32(dst) = as_u32(src) ^ 0x80000000; goto CONV_END; +conv_1234_4329: as_u32(dst) = swab32(as_u32(src)) ^ 0x80; goto CONV_END; +conv_1234_xxx4: as_u8(dst) = as_u32(src) & 0xff; goto CONV_END; +conv_1234_xx43: as_u16(dst) = swab16(as_u32(src)); goto CONV_END; +conv_1234_xx34: as_u16(dst) = as_u32(src) & 0xffff; goto CONV_END; +conv_1234_x432: as_u32(dst) = swab32(as_u32(src)) >> 8; goto CONV_END; +conv_1234_234x: as_u32(dst) = as_u32(src) << 8; goto CONV_END; +conv_1234_xxxC: as_u8(dst) = (as_u32(src) & 0xff) ^ 0x80; goto CONV_END; +conv_1234_xxC3: as_u16(dst) = swab16(as_u32(src) ^ 0x80); goto CONV_END; +conv_1234_xx3C: as_u16(dst) = (as_u32(src) & 0xffff) ^ 0x80; goto CONV_END; +conv_1234_xC32: as_u32(dst) = (swab32(as_u32(src)) >> 8) ^ 0x800000; goto CONV_END; +conv_1234_23Cx: as_u32(dst) = (as_u32(src) ^ 0x80) << 8; goto CONV_END; +conv_1234_C321: as_u32(dst) = swab32(as_u32(src) ^ 0x80); goto CONV_END; +conv_1234_123C: as_u32(dst) = as_u32(src) ^ 0x80; goto CONV_END; +} +#endif + +#ifdef GET_S16_LABELS +/* src_wid src_endswap unsigned */ +static void *get_s16_labels[4 * 2 * 2] = { + &&get_s16_xxx1_xx10, /* 8h -> 16h */ + &&get_s16_xxx1_xx90, /* 8h ^> 16h */ + &&get_s16_xxx1_xx10, /* 8s -> 16h */ + &&get_s16_xxx1_xx90, /* 8s ^> 16h */ + &&get_s16_xx12_xx12, /* 16h -> 16h */ + &&get_s16_xx12_xx92, /* 16h ^> 16h */ + &&get_s16_xx12_xx21, /* 16s -> 16h */ + &&get_s16_xx12_xxA1, /* 16s ^> 16h */ + &&get_s16_x123_xx12, /* 24h -> 16h */ + &&get_s16_x123_xx92, /* 24h ^> 16h */ + &&get_s16_123x_xx32, /* 24s -> 16h */ + &&get_s16_123x_xxB2, /* 24s ^> 16h */ + &&get_s16_1234_xx12, /* 32h -> 16h */ + &&get_s16_1234_xx92, /* 32h ^> 16h */ + &&get_s16_1234_xx43, /* 32s -> 16h */ + &&get_s16_1234_xxC3, /* 32s ^> 16h */ +}; +#endif + +#ifdef GET_S16_END +while(0) { +get_s16_xxx1_xx10: sample = (u_int16_t)as_u8(src) << 8; goto GET_S16_END; +get_s16_xxx1_xx90: sample = (u_int16_t)(as_u8(src) ^ 0x80) << 8; goto GET_S16_END; +get_s16_xx12_xx12: sample = as_u16(src); goto GET_S16_END; +get_s16_xx12_xx92: sample = as_u16(src) ^ 0x8000; goto GET_S16_END; +get_s16_xx12_xx21: sample = swab16(as_u16(src)); goto GET_S16_END; +get_s16_xx12_xxA1: sample = swab16(as_u16(src) ^ 0x80); goto GET_S16_END; +get_s16_x123_xx12: sample = as_u32(src) >> 8; goto GET_S16_END; +get_s16_x123_xx92: sample = (as_u32(src) >> 8) ^ 0x8000; goto GET_S16_END; +get_s16_123x_xx32: sample = swab16(as_u32(src) >> 8); goto GET_S16_END; +get_s16_123x_xxB2: sample = swab16((as_u32(src) >> 8) ^ 0x8000); goto GET_S16_END; +get_s16_1234_xx12: sample = as_u32(src) >> 16; goto GET_S16_END; +get_s16_1234_xx92: sample = (as_u32(src) >> 16) ^ 0x8000; goto GET_S16_END; +get_s16_1234_xx43: sample = swab16(as_u32(src)); goto GET_S16_END; +get_s16_1234_xxC3: sample = swab16(as_u32(src) ^ 0x80); goto GET_S16_END; +} +#endif + +#ifdef PUT_S16_LABELS +/* dst_wid dst_endswap unsigned */ +static void *put_s16_labels[4 * 2 * 2] = { + &&put_s16_xx12_xxx1, /* 16h -> 8h */ + &&put_s16_xx12_xxx9, /* 16h ^> 8h */ + &&put_s16_xx12_xxx1, /* 16h -> 8s */ + &&put_s16_xx12_xxx9, /* 16h ^> 8s */ + &&put_s16_xx12_xx12, /* 16h -> 16h */ + &&put_s16_xx12_xx92, /* 16h ^> 16h */ + &&put_s16_xx12_xx21, /* 16h -> 16s */ + &&put_s16_xx12_xx29, /* 16h ^> 16s */ + &&put_s16_xx12_x120, /* 16h -> 24h */ + &&put_s16_xx12_x920, /* 16h ^> 24h */ + &&put_s16_xx12_021x, /* 16h -> 24s */ + &&put_s16_xx12_029x, /* 16h ^> 24s */ + &&put_s16_xx12_1200, /* 16h -> 32h */ + &&put_s16_xx12_9200, /* 16h ^> 32h */ + &&put_s16_xx12_0021, /* 16h -> 32s */ + &&put_s16_xx12_0029, /* 16h ^> 32s */ +}; +#endif + +#ifdef PUT_S16_END +while (0) { +put_s16_xx12_xxx1: as_u8(dst) = sample >> 8; goto PUT_S16_END; +put_s16_xx12_xxx9: as_u8(dst) = (sample >> 8) ^ 0x80; goto PUT_S16_END; +put_s16_xx12_xx12: as_u16(dst) = sample; goto PUT_S16_END; +put_s16_xx12_xx92: as_u16(dst) = sample ^ 0x8000; goto PUT_S16_END; +put_s16_xx12_xx21: as_u16(dst) = swab16(sample); goto PUT_S16_END; +put_s16_xx12_xx29: as_u16(dst) = swab16(sample) ^ 0x80; goto PUT_S16_END; +put_s16_xx12_x120: as_u32(dst) = (u_int32_t)sample << 8; goto PUT_S16_END; +put_s16_xx12_x920: as_u32(dst) = (u_int32_t)(sample ^ 0x8000) << 8; goto PUT_S16_END; +put_s16_xx12_021x: as_u32(dst) = (u_int32_t)swab16(sample) << 8; goto PUT_S16_END; +put_s16_xx12_029x: as_u32(dst) = (u_int32_t)(swab16(sample) ^ 0x80) << 8; goto PUT_S16_END; +put_s16_xx12_1200: as_u32(dst) = (u_int32_t)sample << 16; goto PUT_S16_END; +put_s16_xx12_9200: as_u32(dst) = (u_int32_t)(sample ^ 0x8000) << 16; goto PUT_S16_END; +put_s16_xx12_0021: as_u32(dst) = (u_int32_t)swab16(sample); goto PUT_S16_END; +put_s16_xx12_0029: as_u32(dst) = (u_int32_t)swab16(sample) ^ 0x80; goto PUT_S16_END; +} +#endif + +#if 0 +#ifdef GET32_LABELS +/* src_wid src_endswap unsigned */ +static void *get32_labels[4 * 2 * 2] = { + &&get32_xxx1_1000, /* 8h -> 32h */ + &&get32_xxx1_9000, /* 8h ^> 32h */ + &&get32_xxx1_1000, /* 8s -> 32h */ + &&get32_xxx1_9000, /* 8s ^> 32h */ + &&get32_xx12_1200, /* 16h -> 32h */ + &&get32_xx12_9200, /* 16h ^> 32h */ + &&get32_xx12_2100, /* 16s -> 32h */ + &&get32_xx12_A100, /* 16s ^> 32h */ + &&get32_x123_1230, /* 24h -> 32h */ + &&get32_x123_9230, /* 24h ^> 32h */ + &&get32_123x_3210, /* 24s -> 32h */ + &&get32_123x_B210, /* 24s ^> 32h */ + &&get32_1234_1234, /* 32h -> 32h */ + &&get32_1234_9234, /* 32h ^> 32h */ + &&get32_1234_4321, /* 32s -> 32h */ + &&get32_1234_C321, /* 32s ^> 32h */ +}; +#endif + +#ifdef GET32_END +while (0) { +get32_xxx1_1000: sample = (u_int32_t)as_u8(src) << 24; goto GET32_END; +get32_xxx1_9000: sample = (u_int32_t)(as_u8(src) ^ 0x80) << 24; goto GET32_END; +get32_xx12_1200: sample = (u_int32_t)as_u16(src) << 16; goto GET32_END; +get32_xx12_9200: sample = (u_int32_t)(as_u16(src) ^ 0x8000) << 16; goto GET32_END; +get32_xx12_2100: sample = (u_int32_t)swab16(as_u16(src)) << 16; goto GET32_END; +get32_xx12_A100: sample = (u_int32_t)swab16(as_u16(src) ^ 0x80) << 16; goto GET32_END; +get32_x123_1230: sample = as_u32(src) << 8; goto GET32_END; +get32_x123_9230: sample = (as_u32(src) << 8) ^ 0x80000000; goto GET32_END; +get32_123x_3210: sample = swab32(as_u32(src) >> 8); goto GET32_END; +get32_123x_B210: sample = swab32((as_u32(src) >> 8) ^ 0x80); goto GET32_END; +get32_1234_1234: sample = as_u32(src); goto GET32_END; +get32_1234_9234: sample = as_u32(src) ^ 0x80000000; goto GET32_END; +get32_1234_4321: sample = swab32(as_u32(src)); goto GET32_END; +get32_1234_C321: sample = swab32(as_u32(src) ^ 0x80); goto GET32_END; +} +#endif +#endif + +#ifdef PUT_U32_LABELS +/* dst_wid dst_endswap unsigned */ +static void *put_u32_labels[4 * 2 * 2] = { + &&put_u32_1234_xxx9, /* u32h -> s8h */ + &&put_u32_1234_xxx1, /* u32h -> u8h */ + &&put_u32_1234_xxx9, /* u32h -> s8s */ + &&put_u32_1234_xxx1, /* u32h -> u8s */ + &&put_u32_1234_xx92, /* u32h -> s16h */ + &&put_u32_1234_xx12, /* u32h -> u16h */ + &&put_u32_1234_xx29, /* u32h -> s16s */ + &&put_u32_1234_xx21, /* u32h -> u16s */ + &&put_u32_1234_x923, /* u32h -> s24h */ + &&put_u32_1234_x123, /* u32h -> u24h */ + &&put_u32_1234_329x, /* u32h -> s24s */ + &&put_u32_1234_321x, /* u32h -> u24s */ + &&put_u32_1234_9234, /* u32h -> s32h */ + &&put_u32_1234_1234, /* u32h -> u32h */ + &&put_u32_1234_4329, /* u32h -> s32s */ + &&put_u32_1234_4321, /* u32h -> u32s */ +}; +#endif + +#ifdef PUT_U32_END +while (0) { +put_u32_1234_xxx1: as_u8(dst) = sample >> 24; goto PUT_U32_END; +put_u32_1234_xxx9: as_u8(dst) = (sample >> 24) ^ 0x80; goto PUT_U32_END; +put_u32_1234_xx12: as_u16(dst) = sample >> 16; goto PUT_U32_END; +put_u32_1234_xx92: as_u16(dst) = (sample >> 16) ^ 0x8000; goto PUT_U32_END; +put_u32_1234_xx21: as_u16(dst) = swab16(sample >> 16); goto PUT_U32_END; +put_u32_1234_xx29: as_u16(dst) = swab16(sample >> 16) ^ 0x80; goto PUT_U32_END; +put_u32_1234_x123: as_u32(dst) = sample >> 8; goto PUT_U32_END; +put_u32_1234_x923: as_u32(dst) = (sample >> 8) ^ 0x800000; goto PUT_U32_END; +put_u32_1234_321x: as_u32(dst) = swab32(sample) << 8; goto PUT_U32_END; +put_u32_1234_329x: as_u32(dst) = (swab32(sample) ^ 0x80) << 8; goto PUT_U32_END; +put_u32_1234_1234: as_u32(dst) = sample; goto PUT_U32_END; +put_u32_1234_9234: as_u32(dst) = sample ^ 0x80000000; goto PUT_U32_END; +put_u32_1234_4321: as_u32(dst) = swab32(sample); goto PUT_U32_END; +put_u32_1234_4329: as_u32(dst) = swab32(sample) ^ 0x80; goto PUT_U32_END; +} +#endif + +#ifdef GET_U_LABELS +/* width endswap unsigned*/ +static void *get_u_labels[4 * 2 * 2] = { + &&get_u_s8, /* s8 -> u8 */ + &&get_u_u8, /* u8 -> u8 */ + &&get_u_s8, /* s8 -> u8 */ + &&get_u_u8, /* u8 -> u8 */ + &&get_u_s16h, /* s16h -> u16h */ + &&get_u_u16h, /* u16h -> u16h */ + &&get_u_s16s, /* s16s -> u16h */ + &&get_u_u16s, /* u16s -> u16h */ + &&get_u_s24h, /* s24h -> u32h */ + &&get_u_u24h, /* u24h -> u32h */ + &&get_u_s24s, /* s24s -> u32h */ + &&get_u_u24s, /* u24s -> u32h */ + &&get_u_s32h, /* s32h -> u32h */ + &&get_u_u32h, /* u32h -> u32h */ + &&get_u_s32s, /* s32s -> u32h */ + &&get_u_u32s, /* u32s -> u32h */ +}; +#endif + +#ifdef GET_U_END +while (0) { +get_u_s8: sample = as_u8(src) ^ 0x80; goto GET_U_END; +get_u_u8: sample = as_u8(src); goto GET_U_END; +get_u_s16h: sample = as_u16(src) ^ 0x8000; goto GET_U_END; +get_u_u16h: sample = as_u16(src); goto GET_U_END; +get_u_s16s: sample = swab16(as_u16(src) ^ 0x80); goto GET_U_END; +get_u_u16s: sample = swab16(as_u16(src)); goto GET_U_END; +get_u_s24h: sample = (as_u32(src) ^ 0x800000); goto GET_U_END; +get_u_u24h: sample = as_u32(src); goto GET_U_END; +get_u_s24s: sample = swab32(as_u32(src) ^ 0x800000); goto GET_U_END; +get_u_u24s: sample = swab32(as_u32(src)); goto GET_U_END; +get_u_s32h: sample = as_u32(src) ^ 0x80000000; goto GET_U_END; +get_u_u32h: sample = as_u32(src); goto GET_U_END; +get_u_s32s: sample = swab32(as_u32(src) ^ 0x80); goto GET_U_END; +get_u_u32s: sample = swab32(as_u32(src)); goto GET_U_END; +} +#endif + +#if 0 +#ifdef PUT_LABELS +/* width endswap unsigned */ +static void *put_labels[4 * 2 * 2] = { + &&put_s8, /* s8 -> s8 */ + &&put_u8, /* u8 -> s8 */ + &&put_s8, /* s8 -> s8 */ + &&put_u8, /* u8 -> s8 */ + &&put_s16h, /* s16h -> s16h */ + &&put_u16h, /* u16h -> s16h */ + &&put_s16s, /* s16s -> s16h */ + &&put_u16s, /* u16s -> s16h */ + &&put_s24h, /* s24h -> s32h */ + &&put_u24h, /* u24h -> s32h */ + &&put_s24s, /* s24s -> s32h */ + &&put_u24s, /* u24s -> s32h */ + &&put_s32h, /* s32h -> s32h */ + &&put_u32h, /* u32h -> s32h */ + &&put_s32s, /* s32s -> s32h */ + &&put_u32s, /* u32s -> s32h */ +}; +#endif + +#ifdef PUT_END +put_s8: as_s8(dst) = sample; goto PUT_END; +put_u8: as_u8(dst) = sample ^ 0x80; goto PUT_END; +put_s16h: as_s16(dst) = sample; goto PUT_END; +put_u16h: as_u16(dst) = sample ^ 0x8000; goto PUT_END; +put_s16s: as_s16(dst) = swab16(sample); goto PUT_END; +put_u16s: as_u16(dst) = swab16(sample ^ 0x80); goto PUT_END; +put_s24h: as_s24(dst) = sample & 0xffffff; goto PUT_END; +put_u24h: as_u24(dst) = sample ^ 0x80000000; goto PUT_END; +put_s24s: as_s24(dst) = swab32(sample & 0xffffff); goto PUT_END; +put_u24s: as_u24(dst) = swab32(sample ^ 0x80); goto PUT_END; +put_s32h: as_s32(dst) = sample; goto PUT_END; +put_u32h: as_u32(dst) = sample ^ 0x80000000; goto PUT_END; +put_s32s: as_s32(dst) = swab32(sample); goto PUT_END; +put_u32s: as_u32(dst) = swab32(sample ^ 0x80); goto PUT_END; +#endif +#endif + +#undef as_u8 +#undef as_u16 +#undef as_u32 +#undef as_s8 +#undef as_s16 +#undef as_s32 diff --git a/sound/core/oss/rate.c b/sound/core/oss/rate.c new file mode 100644 index 000000000000..1096ec186714 --- /dev/null +++ b/sound/core/oss/rate.c @@ -0,0 +1,378 @@ +/* + * Rate conversion Plug-In + * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "pcm_plugin.h" + +#define SHIFT 11 +#define BITS (1<<SHIFT) +#define R_MASK (BITS-1) + +/* + * Basic rate conversion plugin + */ + +typedef struct { + signed short last_S1; + signed short last_S2; +} rate_channel_t; + +typedef void (*rate_f)(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + int src_frames, int dst_frames); + +typedef struct rate_private_data { + unsigned int pitch; + unsigned int pos; + rate_f func; + int get, put; + snd_pcm_sframes_t old_src_frames, old_dst_frames; + rate_channel_t channels[0]; +} rate_t; + +static void rate_init(snd_pcm_plugin_t *plugin) +{ + unsigned int channel; + rate_t *data = (rate_t *)plugin->extra_data; + data->pos = 0; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + data->channels[channel].last_S1 = 0; + data->channels[channel].last_S2 = 0; + } +} + +static void resample_expand(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + int src_frames, int dst_frames) +{ + unsigned int pos = 0; + signed int val; + signed short S1, S2; + char *src, *dst; + unsigned int channel; + int src_step, dst_step; + int src_frames1, dst_frames1; + rate_t *data = (rate_t *)plugin->extra_data; + rate_channel_t *rchannels = data->channels; + +#define GET_S16_LABELS +#define PUT_S16_LABELS +#include "plugin_ops.h" +#undef GET_S16_LABELS +#undef PUT_S16_LABELS + void *get = get_s16_labels[data->get]; + void *put = put_s16_labels[data->put]; + signed short sample = 0; + + for (channel = 0; channel < plugin->src_format.channels; channel++) { + pos = data->pos; + S1 = rchannels->last_S1; + S2 = rchannels->last_S2; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = (char *)src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = (char *)dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + src_frames1 = src_frames; + dst_frames1 = dst_frames; + while (dst_frames1-- > 0) { + if (pos & ~R_MASK) { + pos &= R_MASK; + S1 = S2; + if (src_frames1-- > 0) { + goto *get; +#define GET_S16_END after_get +#include "plugin_ops.h" +#undef GET_S16_END + after_get: + S2 = sample; + src += src_step; + } + } + val = S1 + ((S2 - S1) * (signed int)pos) / BITS; + if (val < -32768) + val = -32768; + else if (val > 32767) + val = 32767; + sample = val; + goto *put; +#define PUT_S16_END after_put +#include "plugin_ops.h" +#undef PUT_S16_END + after_put: + dst += dst_step; + pos += data->pitch; + } + rchannels->last_S1 = S1; + rchannels->last_S2 = S2; + rchannels++; + } + data->pos = pos; +} + +static void resample_shrink(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + int src_frames, int dst_frames) +{ + unsigned int pos = 0; + signed int val; + signed short S1, S2; + char *src, *dst; + unsigned int channel; + int src_step, dst_step; + int src_frames1, dst_frames1; + rate_t *data = (rate_t *)plugin->extra_data; + rate_channel_t *rchannels = data->channels; + +#define GET_S16_LABELS +#define PUT_S16_LABELS +#include "plugin_ops.h" +#undef GET_S16_LABELS +#undef PUT_S16_LABELS + void *get = get_s16_labels[data->get]; + void *put = put_s16_labels[data->put]; + signed short sample = 0; + + for (channel = 0; channel < plugin->src_format.channels; ++channel) { + pos = data->pos; + S1 = rchannels->last_S1; + S2 = rchannels->last_S2; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = (char *)src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = (char *)dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + src_frames1 = src_frames; + dst_frames1 = dst_frames; + while (dst_frames1 > 0) { + S1 = S2; + if (src_frames1-- > 0) { + goto *get; +#define GET_S16_END after_get +#include "plugin_ops.h" +#undef GET_S16_END + after_get: + S2 = sample; + src += src_step; + } + if (pos & ~R_MASK) { + pos &= R_MASK; + val = S1 + ((S2 - S1) * (signed int)pos) / BITS; + if (val < -32768) + val = -32768; + else if (val > 32767) + val = 32767; + sample = val; + goto *put; +#define PUT_S16_END after_put +#include "plugin_ops.h" +#undef PUT_S16_END + after_put: + dst += dst_step; + dst_frames1--; + } + pos += data->pitch; + } + rchannels->last_S1 = S1; + rchannels->last_S2 = S2; + rchannels++; + } + data->pos = pos; +} + +static snd_pcm_sframes_t rate_src_frames(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames) +{ + rate_t *data; + snd_pcm_sframes_t res; + + snd_assert(plugin != NULL, return -ENXIO); + if (frames == 0) + return 0; + data = (rate_t *)plugin->extra_data; + if (plugin->src_format.rate < plugin->dst_format.rate) { + res = (((frames * data->pitch) + (BITS/2)) >> SHIFT); + } else { + res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch); + } + if (data->old_src_frames > 0) { + snd_pcm_sframes_t frames1 = frames, res1 = data->old_dst_frames; + while (data->old_src_frames < frames1) { + frames1 >>= 1; + res1 <<= 1; + } + while (data->old_src_frames > frames1) { + frames1 <<= 1; + res1 >>= 1; + } + if (data->old_src_frames == frames1) + return res1; + } + data->old_src_frames = frames; + data->old_dst_frames = res; + return res; +} + +static snd_pcm_sframes_t rate_dst_frames(snd_pcm_plugin_t *plugin, snd_pcm_uframes_t frames) +{ + rate_t *data; + snd_pcm_sframes_t res; + + snd_assert(plugin != NULL, return -ENXIO); + if (frames == 0) + return 0; + data = (rate_t *)plugin->extra_data; + if (plugin->src_format.rate < plugin->dst_format.rate) { + res = (((frames << SHIFT) + (data->pitch / 2)) / data->pitch); + } else { + res = (((frames * data->pitch) + (BITS/2)) >> SHIFT); + } + if (data->old_dst_frames > 0) { + snd_pcm_sframes_t frames1 = frames, res1 = data->old_src_frames; + while (data->old_dst_frames < frames1) { + frames1 >>= 1; + res1 <<= 1; + } + while (data->old_dst_frames > frames1) { + frames1 <<= 1; + res1 >>= 1; + } + if (data->old_dst_frames == frames1) + return res1; + } + data->old_dst_frames = frames; + data->old_src_frames = res; + return res; +} + +static snd_pcm_sframes_t rate_transfer(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames) +{ + snd_pcm_uframes_t dst_frames; + rate_t *data; + + snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO); + if (frames == 0) + return 0; +#ifdef CONFIG_SND_DEBUG + { + unsigned int channel; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + snd_assert(src_channels[channel].area.first % 8 == 0 && + src_channels[channel].area.step % 8 == 0, + return -ENXIO); + snd_assert(dst_channels[channel].area.first % 8 == 0 && + dst_channels[channel].area.step % 8 == 0, + return -ENXIO); + } + } +#endif + + dst_frames = rate_dst_frames(plugin, frames); + if (dst_frames > dst_channels[0].frames) + dst_frames = dst_channels[0].frames; + data = (rate_t *)plugin->extra_data; + data->func(plugin, src_channels, dst_channels, frames, dst_frames); + return dst_frames; +} + +static int rate_action(snd_pcm_plugin_t *plugin, + snd_pcm_plugin_action_t action, + unsigned long udata ATTRIBUTE_UNUSED) +{ + snd_assert(plugin != NULL, return -ENXIO); + switch (action) { + case INIT: + case PREPARE: + rate_init(plugin); + break; + default: + break; + } + return 0; /* silenty ignore other actions */ +} + +int snd_pcm_plugin_build_rate(snd_pcm_plug_t *plug, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + snd_pcm_plugin_t **r_plugin) +{ + int err; + rate_t *data; + snd_pcm_plugin_t *plugin; + + snd_assert(r_plugin != NULL, return -ENXIO); + *r_plugin = NULL; + + snd_assert(src_format->channels == dst_format->channels, return -ENXIO); + snd_assert(src_format->channels > 0, return -ENXIO); + snd_assert(snd_pcm_format_linear(src_format->format) != 0, return -ENXIO); + snd_assert(snd_pcm_format_linear(dst_format->format) != 0, return -ENXIO); + snd_assert(src_format->rate != dst_format->rate, return -ENXIO); + + err = snd_pcm_plugin_build(plug, "rate conversion", + src_format, dst_format, + sizeof(rate_t) + src_format->channels * sizeof(rate_channel_t), + &plugin); + if (err < 0) + return err; + data = (rate_t *)plugin->extra_data; + data->get = getput_index(src_format->format); + snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL); + data->put = getput_index(dst_format->format); + snd_assert(data->put >= 0 && data->put < 4*2*2, return -EINVAL); + + if (src_format->rate < dst_format->rate) { + data->pitch = ((src_format->rate << SHIFT) + (dst_format->rate >> 1)) / dst_format->rate; + data->func = resample_expand; + } else { + data->pitch = ((dst_format->rate << SHIFT) + (src_format->rate >> 1)) / src_format->rate; + data->func = resample_shrink; + } + data->pos = 0; + rate_init(plugin); + data->old_src_frames = data->old_dst_frames = 0; + plugin->transfer = rate_transfer; + plugin->src_frames = rate_src_frames; + plugin->dst_frames = rate_dst_frames; + plugin->action = rate_action; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/route.c b/sound/core/oss/route.c new file mode 100644 index 000000000000..c955b7dfdb30 --- /dev/null +++ b/sound/core/oss/route.c @@ -0,0 +1,519 @@ +/* + * Attenuated route Plug-In + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "pcm_plugin.h" + +/* The best possible hack to support missing optimization in gcc 2.7.2.3 */ +#if ROUTE_PLUGIN_RESOLUTION & (ROUTE_PLUGIN_RESOLUTION - 1) != 0 +#define div(a) a /= ROUTE_PLUGIN_RESOLUTION +#elif ROUTE_PLUGIN_RESOLUTION == 16 +#define div(a) a >>= 4 +#else +#error "Add some code here" +#endif + +typedef struct ttable_dst ttable_dst_t; +typedef struct route_private_data route_t; + +typedef void (*route_channel_f)(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channel, + ttable_dst_t* ttable, snd_pcm_uframes_t frames); + +typedef struct { + int channel; + int as_int; +} ttable_src_t; + +struct ttable_dst { + int att; /* Attenuated */ + unsigned int nsrcs; + ttable_src_t* srcs; + route_channel_f func; +}; + +struct route_private_data { + enum {R_UINT32=0, R_UINT64=1} sum_type; + int get, put; + int conv; + int src_sample_size; + ttable_dst_t ttable[0]; +}; + +typedef union { + u_int32_t as_uint32; + u_int64_t as_uint64; +} sum_t; + + +static void route_to_channel_from_zero(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels ATTRIBUTE_UNUSED, + snd_pcm_plugin_channel_t *dst_channel, + ttable_dst_t* ttable ATTRIBUTE_UNUSED, snd_pcm_uframes_t frames) +{ + if (dst_channel->wanted) + snd_pcm_area_silence(&dst_channel->area, 0, frames, plugin->dst_format.format); + dst_channel->enabled = 0; +} + +static void route_to_channel_from_one(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channel, + ttable_dst_t* ttable, snd_pcm_uframes_t frames) +{ +#define CONV_LABELS +#include "plugin_ops.h" +#undef CONV_LABELS + route_t *data = (route_t *)plugin->extra_data; + void *conv; + const snd_pcm_plugin_channel_t *src_channel = NULL; + unsigned int srcidx; + char *src, *dst; + int src_step, dst_step; + for (srcidx = 0; srcidx < ttable->nsrcs; ++srcidx) { + src_channel = &src_channels[ttable->srcs[srcidx].channel]; + if (src_channel->area.addr != NULL) + break; + } + if (srcidx == ttable->nsrcs) { + route_to_channel_from_zero(plugin, src_channels, dst_channel, ttable, frames); + return; + } + + dst_channel->enabled = 1; + conv = conv_labels[data->conv]; + src = src_channel->area.addr + src_channel->area.first / 8; + src_step = src_channel->area.step / 8; + dst = dst_channel->area.addr + dst_channel->area.first / 8; + dst_step = dst_channel->area.step / 8; + while (frames-- > 0) { + goto *conv; +#define CONV_END after +#include "plugin_ops.h" +#undef CONV_END + after: + src += src_step; + dst += dst_step; + } +} + +static void route_to_channel(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channel, + ttable_dst_t* ttable, snd_pcm_uframes_t frames) +{ +#define GET_U_LABELS +#define PUT_U32_LABELS +#include "plugin_ops.h" +#undef GET_U_LABELS +#undef PUT_U32_LABELS + static void *zero_labels[2] = { &&zero_int32, &&zero_int64 }; + /* sum_type att */ + static void *add_labels[2 * 2] = { &&add_int32_noatt, &&add_int32_att, + &&add_int64_noatt, &&add_int64_att, + }; + /* sum_type att shift */ + static void *norm_labels[2 * 2 * 4] = { NULL, + &&norm_int32_8_noatt, + &&norm_int32_16_noatt, + &&norm_int32_24_noatt, + NULL, + &&norm_int32_8_att, + &&norm_int32_16_att, + &&norm_int32_24_att, + &&norm_int64_0_noatt, + &&norm_int64_8_noatt, + &&norm_int64_16_noatt, + &&norm_int64_24_noatt, + &&norm_int64_0_att, + &&norm_int64_8_att, + &&norm_int64_16_att, + &&norm_int64_24_att, + }; + route_t *data = (route_t *)plugin->extra_data; + void *zero, *get, *add, *norm, *put_u32; + int nsrcs = ttable->nsrcs; + char *dst; + int dst_step; + char *srcs[nsrcs]; + int src_steps[nsrcs]; + ttable_src_t src_tt[nsrcs]; + u_int32_t sample = 0; + int srcidx, srcidx1 = 0; + for (srcidx = 0; srcidx < nsrcs; ++srcidx) { + const snd_pcm_plugin_channel_t *src_channel = &src_channels[ttable->srcs[srcidx].channel]; + if (!src_channel->enabled) + continue; + srcs[srcidx1] = src_channel->area.addr + src_channel->area.first / 8; + src_steps[srcidx1] = src_channel->area.step / 8; + src_tt[srcidx1] = ttable->srcs[srcidx]; + srcidx1++; + } + nsrcs = srcidx1; + if (nsrcs == 0) { + route_to_channel_from_zero(plugin, src_channels, dst_channel, ttable, frames); + return; + } else if (nsrcs == 1 && src_tt[0].as_int == ROUTE_PLUGIN_RESOLUTION) { + route_to_channel_from_one(plugin, src_channels, dst_channel, ttable, frames); + return; + } + + dst_channel->enabled = 1; + zero = zero_labels[data->sum_type]; + get = get_u_labels[data->get]; + add = add_labels[data->sum_type * 2 + ttable->att]; + norm = norm_labels[data->sum_type * 8 + ttable->att * 4 + 4 - data->src_sample_size]; + put_u32 = put_u32_labels[data->put]; + dst = dst_channel->area.addr + dst_channel->area.first / 8; + dst_step = dst_channel->area.step / 8; + + while (frames-- > 0) { + ttable_src_t *ttp = src_tt; + sum_t sum; + + /* Zero sum */ + goto *zero; + zero_int32: + sum.as_uint32 = 0; + goto zero_end; + zero_int64: + sum.as_uint64 = 0; + goto zero_end; + zero_end: + for (srcidx = 0; srcidx < nsrcs; ++srcidx) { + char *src = srcs[srcidx]; + + /* Get sample */ + goto *get; +#define GET_U_END after_get +#include "plugin_ops.h" +#undef GET_U_END + after_get: + + /* Sum */ + goto *add; + add_int32_att: + sum.as_uint32 += sample * ttp->as_int; + goto after_sum; + add_int32_noatt: + if (ttp->as_int) + sum.as_uint32 += sample; + goto after_sum; + add_int64_att: + sum.as_uint64 += (u_int64_t) sample * ttp->as_int; + goto after_sum; + add_int64_noatt: + if (ttp->as_int) + sum.as_uint64 += sample; + goto after_sum; + after_sum: + srcs[srcidx] += src_steps[srcidx]; + ttp++; + } + + /* Normalization */ + goto *norm; + norm_int32_8_att: + sum.as_uint64 = sum.as_uint32; + norm_int64_8_att: + sum.as_uint64 <<= 8; + norm_int64_0_att: + div(sum.as_uint64); + goto norm_int; + + norm_int32_16_att: + sum.as_uint64 = sum.as_uint32; + norm_int64_16_att: + sum.as_uint64 <<= 16; + div(sum.as_uint64); + goto norm_int; + + norm_int32_24_att: + sum.as_uint64 = sum.as_uint32; + norm_int64_24_att: + sum.as_uint64 <<= 24; + div(sum.as_uint64); + goto norm_int; + + norm_int32_8_noatt: + sum.as_uint64 = sum.as_uint32; + norm_int64_8_noatt: + sum.as_uint64 <<= 8; + goto norm_int; + + norm_int32_16_noatt: + sum.as_uint64 = sum.as_uint32; + norm_int64_16_noatt: + sum.as_uint64 <<= 16; + goto norm_int; + + norm_int32_24_noatt: + sum.as_uint64 = sum.as_uint32; + norm_int64_24_noatt: + sum.as_uint64 <<= 24; + goto norm_int; + + norm_int64_0_noatt: + norm_int: + if (sum.as_uint64 > (u_int32_t)0xffffffff) + sample = (u_int32_t)0xffffffff; + else + sample = sum.as_uint64; + goto after_norm; + + after_norm: + + /* Put sample */ + goto *put_u32; +#define PUT_U32_END after_put_u32 +#include "plugin_ops.h" +#undef PUT_U32_END + after_put_u32: + + dst += dst_step; + } +} + +static int route_src_channels_mask(snd_pcm_plugin_t *plugin, + bitset_t *dst_vmask, + bitset_t **src_vmask) +{ + route_t *data = (route_t *)plugin->extra_data; + int schannels = plugin->src_format.channels; + int dchannels = plugin->dst_format.channels; + bitset_t *vmask = plugin->src_vmask; + int channel; + ttable_dst_t *dp = data->ttable; + bitset_zero(vmask, schannels); + for (channel = 0; channel < dchannels; channel++, dp++) { + unsigned int src; + ttable_src_t *sp; + if (!bitset_get(dst_vmask, channel)) + continue; + sp = dp->srcs; + for (src = 0; src < dp->nsrcs; src++, sp++) + bitset_set(vmask, sp->channel); + } + *src_vmask = vmask; + return 0; +} + +static int route_dst_channels_mask(snd_pcm_plugin_t *plugin, + bitset_t *src_vmask, + bitset_t **dst_vmask) +{ + route_t *data = (route_t *)plugin->extra_data; + int dchannels = plugin->dst_format.channels; + bitset_t *vmask = plugin->dst_vmask; + int channel; + ttable_dst_t *dp = data->ttable; + bitset_zero(vmask, dchannels); + for (channel = 0; channel < dchannels; channel++, dp++) { + unsigned int src; + ttable_src_t *sp; + sp = dp->srcs; + for (src = 0; src < dp->nsrcs; src++, sp++) { + if (bitset_get(src_vmask, sp->channel)) { + bitset_set(vmask, channel); + break; + } + } + } + *dst_vmask = vmask; + return 0; +} + +static void route_free(snd_pcm_plugin_t *plugin) +{ + route_t *data = (route_t *)plugin->extra_data; + unsigned int dst_channel; + for (dst_channel = 0; dst_channel < plugin->dst_format.channels; ++dst_channel) { + kfree(data->ttable[dst_channel].srcs); + } +} + +static int route_load_ttable(snd_pcm_plugin_t *plugin, + const route_ttable_entry_t* src_ttable) +{ + route_t *data; + unsigned int src_channel, dst_channel; + const route_ttable_entry_t *sptr; + ttable_dst_t *dptr; + if (src_ttable == NULL) + return 0; + data = (route_t *)plugin->extra_data; + dptr = data->ttable; + sptr = src_ttable; + plugin->private_free = route_free; + for (dst_channel = 0; dst_channel < plugin->dst_format.channels; ++dst_channel) { + route_ttable_entry_t t = 0; + int att = 0; + int nsrcs = 0; + ttable_src_t srcs[plugin->src_format.channels]; + for (src_channel = 0; src_channel < plugin->src_format.channels; ++src_channel) { + snd_assert(*sptr >= 0 || *sptr <= FULL, return -ENXIO); + if (*sptr != 0) { + srcs[nsrcs].channel = src_channel; + srcs[nsrcs].as_int = *sptr; + if (*sptr != FULL) + att = 1; + t += *sptr; + nsrcs++; + } + sptr++; + } + dptr->att = att; + dptr->nsrcs = nsrcs; + if (nsrcs == 0) + dptr->func = route_to_channel_from_zero; + else if (nsrcs == 1 && !att) + dptr->func = route_to_channel_from_one; + else + dptr->func = route_to_channel; + if (nsrcs > 0) { + int srcidx; + dptr->srcs = kcalloc(nsrcs, sizeof(*srcs), GFP_KERNEL); + for(srcidx = 0; srcidx < nsrcs; srcidx++) + dptr->srcs[srcidx] = srcs[srcidx]; + } else + dptr->srcs = NULL; + dptr++; + } + return 0; +} + +static snd_pcm_sframes_t route_transfer(snd_pcm_plugin_t *plugin, + const snd_pcm_plugin_channel_t *src_channels, + snd_pcm_plugin_channel_t *dst_channels, + snd_pcm_uframes_t frames) +{ + route_t *data; + int src_nchannels, dst_nchannels; + int dst_channel; + ttable_dst_t *ttp; + snd_pcm_plugin_channel_t *dvp; + + snd_assert(plugin != NULL && src_channels != NULL && dst_channels != NULL, return -ENXIO); + if (frames == 0) + return 0; + data = (route_t *)plugin->extra_data; + + src_nchannels = plugin->src_format.channels; + dst_nchannels = plugin->dst_format.channels; + +#ifdef CONFIG_SND_DEBUG + { + int src_channel; + for (src_channel = 0; src_channel < src_nchannels; ++src_channel) { + snd_assert(src_channels[src_channel].area.first % 8 == 0 || + src_channels[src_channel].area.step % 8 == 0, + return -ENXIO); + } + for (dst_channel = 0; dst_channel < dst_nchannels; ++dst_channel) { + snd_assert(dst_channels[dst_channel].area.first % 8 == 0 || + dst_channels[dst_channel].area.step % 8 == 0, + return -ENXIO); + } + } +#endif + + ttp = data->ttable; + dvp = dst_channels; + for (dst_channel = 0; dst_channel < dst_nchannels; ++dst_channel) { + ttp->func(plugin, src_channels, dvp, ttp, frames); + dvp++; + ttp++; + } + return frames; +} + +int getput_index(int format) +{ + int sign, width, endian; + sign = !snd_pcm_format_signed(format); + width = snd_pcm_format_width(format) / 8 - 1; + if (width < 0 || width > 3) { + snd_printk(KERN_ERR "snd-pcm-oss: invalid format %d\n", format); + width = 0; + } +#ifdef SNDRV_LITTLE_ENDIAN + endian = snd_pcm_format_big_endian(format); +#else + endian = snd_pcm_format_little_endian(format); +#endif + if (endian < 0) + endian = 0; + return width * 4 + endian * 2 + sign; +} + +int snd_pcm_plugin_build_route(snd_pcm_plug_t *plug, + snd_pcm_plugin_format_t *src_format, + snd_pcm_plugin_format_t *dst_format, + route_ttable_entry_t *ttable, + snd_pcm_plugin_t **r_plugin) +{ + route_t *data; + snd_pcm_plugin_t *plugin; + int err; + + snd_assert(r_plugin != NULL, return -ENXIO); + *r_plugin = NULL; + snd_assert(src_format->rate == dst_format->rate, return -ENXIO); + snd_assert(snd_pcm_format_linear(src_format->format) != 0 && + snd_pcm_format_linear(dst_format->format) != 0, + return -ENXIO); + + err = snd_pcm_plugin_build(plug, "attenuated route conversion", + src_format, dst_format, + sizeof(route_t) + sizeof(data->ttable[0]) * dst_format->channels, + &plugin); + if (err < 0) + return err; + + data = (route_t *) plugin->extra_data; + + data->get = getput_index(src_format->format); + snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL); + data->put = getput_index(dst_format->format); + snd_assert(data->get >= 0 && data->get < 4*2*2, return -EINVAL); + data->conv = conv_index(src_format->format, dst_format->format); + + if (snd_pcm_format_width(src_format->format) == 32) + data->sum_type = R_UINT64; + else + data->sum_type = R_UINT32; + data->src_sample_size = snd_pcm_format_width(src_format->format) / 8; + + if ((err = route_load_ttable(plugin, ttable)) < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + plugin->transfer = route_transfer; + plugin->src_channels_mask = route_src_channels_mask; + plugin->dst_channels_mask = route_dst_channels_mask; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/pcm.c b/sound/core/pcm.c new file mode 100644 index 000000000000..8d94325529a8 --- /dev/null +++ b/sound/core/pcm.c @@ -0,0 +1,1074 @@ +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/pcm.h> +#include <sound/control.h> +#include <sound/info.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Abramo Bagnara <abramo@alsa-project.org>"); +MODULE_DESCRIPTION("Midlevel PCM code for ALSA."); +MODULE_LICENSE("GPL"); + +snd_pcm_t *snd_pcm_devices[SNDRV_CARDS * SNDRV_PCM_DEVICES]; +static LIST_HEAD(snd_pcm_notify_list); +static DECLARE_MUTEX(register_mutex); + +static int snd_pcm_free(snd_pcm_t *pcm); +static int snd_pcm_dev_free(snd_device_t *device); +static int snd_pcm_dev_register(snd_device_t *device); +static int snd_pcm_dev_disconnect(snd_device_t *device); +static int snd_pcm_dev_unregister(snd_device_t *device); + +static int snd_pcm_control_ioctl(snd_card_t * card, + snd_ctl_file_t * control, + unsigned int cmd, unsigned long arg) +{ + unsigned int tmp; + + tmp = card->number * SNDRV_PCM_DEVICES; + switch (cmd) { + case SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE: + { + int device; + + if (get_user(device, (int __user *)arg)) + return -EFAULT; + device = device < 0 ? 0 : device + 1; + while (device < SNDRV_PCM_DEVICES) { + if (snd_pcm_devices[tmp + device]) + break; + device++; + } + if (device == SNDRV_PCM_DEVICES) + device = -1; + if (put_user(device, (int __user *)arg)) + return -EFAULT; + return 0; + } + case SNDRV_CTL_IOCTL_PCM_INFO: + { + snd_pcm_info_t __user *info; + unsigned int device, subdevice; + snd_pcm_stream_t stream; + snd_pcm_t *pcm; + snd_pcm_str_t *pstr; + snd_pcm_substream_t *substream; + info = (snd_pcm_info_t __user *)arg; + if (get_user(device, &info->device)) + return -EFAULT; + if (device >= SNDRV_PCM_DEVICES) + return -ENXIO; + pcm = snd_pcm_devices[tmp + device]; + if (pcm == NULL) + return -ENXIO; + if (get_user(stream, &info->stream)) + return -EFAULT; + if (stream < 0 || stream > 1) + return -EINVAL; + pstr = &pcm->streams[stream]; + if (pstr->substream_count == 0) + return -ENOENT; + if (get_user(subdevice, &info->subdevice)) + return -EFAULT; + if (subdevice >= pstr->substream_count) + return -ENXIO; + for (substream = pstr->substream; substream; substream = substream->next) + if (substream->number == (int)subdevice) + break; + if (substream == NULL) + return -ENXIO; + return snd_pcm_info_user(substream, info); + } + case SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE: + { + int val; + + if (get_user(val, (int __user *)arg)) + return -EFAULT; + control->prefer_pcm_subdevice = val; + return 0; + } + } + return -ENOIOCTLCMD; +} +#define STATE(v) [SNDRV_PCM_STATE_##v] = #v +#define STREAM(v) [SNDRV_PCM_STREAM_##v] = #v +#define READY(v) [SNDRV_PCM_READY_##v] = #v +#define XRUN(v) [SNDRV_PCM_XRUN_##v] = #v +#define SILENCE(v) [SNDRV_PCM_SILENCE_##v] = #v +#define TSTAMP(v) [SNDRV_PCM_TSTAMP_##v] = #v +#define ACCESS(v) [SNDRV_PCM_ACCESS_##v] = #v +#define START(v) [SNDRV_PCM_START_##v] = #v +#define FORMAT(v) [SNDRV_PCM_FORMAT_##v] = #v +#define SUBFORMAT(v) [SNDRV_PCM_SUBFORMAT_##v] = #v + +static char *snd_pcm_stream_names[] = { + STREAM(PLAYBACK), + STREAM(CAPTURE), +}; + +static char *snd_pcm_state_names[] = { + STATE(OPEN), + STATE(SETUP), + STATE(PREPARED), + STATE(RUNNING), + STATE(XRUN), + STATE(DRAINING), + STATE(PAUSED), + STATE(SUSPENDED), +}; + +static char *snd_pcm_access_names[] = { + ACCESS(MMAP_INTERLEAVED), + ACCESS(MMAP_NONINTERLEAVED), + ACCESS(MMAP_COMPLEX), + ACCESS(RW_INTERLEAVED), + ACCESS(RW_NONINTERLEAVED), +}; + +static char *snd_pcm_format_names[] = { + FORMAT(S8), + FORMAT(U8), + FORMAT(S16_LE), + FORMAT(S16_BE), + FORMAT(U16_LE), + FORMAT(U16_BE), + FORMAT(S24_LE), + FORMAT(S24_BE), + FORMAT(U24_LE), + FORMAT(U24_BE), + FORMAT(S32_LE), + FORMAT(S32_BE), + FORMAT(U32_LE), + FORMAT(U32_BE), + FORMAT(FLOAT_LE), + FORMAT(FLOAT_BE), + FORMAT(FLOAT64_LE), + FORMAT(FLOAT64_BE), + FORMAT(IEC958_SUBFRAME_LE), + FORMAT(IEC958_SUBFRAME_BE), + FORMAT(MU_LAW), + FORMAT(A_LAW), + FORMAT(IMA_ADPCM), + FORMAT(MPEG), + FORMAT(GSM), + FORMAT(SPECIAL), + FORMAT(S24_3LE), + FORMAT(S24_3BE), + FORMAT(U24_3LE), + FORMAT(U24_3BE), + FORMAT(S20_3LE), + FORMAT(S20_3BE), + FORMAT(U20_3LE), + FORMAT(U20_3BE), + FORMAT(S18_3LE), + FORMAT(S18_3BE), + FORMAT(U18_3LE), + FORMAT(U18_3BE), +}; + +static char *snd_pcm_subformat_names[] = { + SUBFORMAT(STD), +}; + +static char *snd_pcm_tstamp_mode_names[] = { + TSTAMP(NONE), + TSTAMP(MMAP), +}; + +static const char *snd_pcm_stream_name(snd_pcm_stream_t stream) +{ + snd_assert(stream <= SNDRV_PCM_STREAM_LAST, return NULL); + return snd_pcm_stream_names[stream]; +} + +static const char *snd_pcm_access_name(snd_pcm_access_t access) +{ + snd_assert(access <= SNDRV_PCM_ACCESS_LAST, return NULL); + return snd_pcm_access_names[access]; +} + +const char *snd_pcm_format_name(snd_pcm_format_t format) +{ + snd_assert(format <= SNDRV_PCM_FORMAT_LAST, return NULL); + return snd_pcm_format_names[format]; +} + +static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat) +{ + snd_assert(subformat <= SNDRV_PCM_SUBFORMAT_LAST, return NULL); + return snd_pcm_subformat_names[subformat]; +} + +static const char *snd_pcm_tstamp_mode_name(snd_pcm_tstamp_t mode) +{ + snd_assert(mode <= SNDRV_PCM_TSTAMP_LAST, return NULL); + return snd_pcm_tstamp_mode_names[mode]; +} + +static const char *snd_pcm_state_name(snd_pcm_state_t state) +{ + snd_assert(state <= SNDRV_PCM_STATE_LAST, return NULL); + return snd_pcm_state_names[state]; +} + +#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) +#include <linux/soundcard.h> +static const char *snd_pcm_oss_format_name(int format) +{ + switch (format) { + case AFMT_MU_LAW: + return "MU_LAW"; + case AFMT_A_LAW: + return "A_LAW"; + case AFMT_IMA_ADPCM: + return "IMA_ADPCM"; + case AFMT_U8: + return "U8"; + case AFMT_S16_LE: + return "S16_LE"; + case AFMT_S16_BE: + return "S16_BE"; + case AFMT_S8: + return "S8"; + case AFMT_U16_LE: + return "U16_LE"; + case AFMT_U16_BE: + return "U16_BE"; + case AFMT_MPEG: + return "MPEG"; + default: + return "unknown"; + } +} +#endif + +#ifdef CONFIG_PROC_FS +static void snd_pcm_proc_info_read(snd_pcm_substream_t *substream, snd_info_buffer_t *buffer) +{ + snd_pcm_info_t *info; + int err; + + snd_runtime_check(substream, return); + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) { + printk(KERN_DEBUG "snd_pcm_proc_info_read: cannot malloc\n"); + return; + } + + err = snd_pcm_info(substream, info); + if (err < 0) { + snd_iprintf(buffer, "error %d\n", err); + kfree(info); + return; + } + snd_iprintf(buffer, "card: %d\n", info->card); + snd_iprintf(buffer, "device: %d\n", info->device); + snd_iprintf(buffer, "subdevice: %d\n", info->subdevice); + snd_iprintf(buffer, "stream: %s\n", snd_pcm_stream_name(info->stream)); + snd_iprintf(buffer, "id: %s\n", info->id); + snd_iprintf(buffer, "name: %s\n", info->name); + snd_iprintf(buffer, "subname: %s\n", info->subname); + snd_iprintf(buffer, "class: %d\n", info->dev_class); + snd_iprintf(buffer, "subclass: %d\n", info->dev_subclass); + snd_iprintf(buffer, "subdevices_count: %d\n", info->subdevices_count); + snd_iprintf(buffer, "subdevices_avail: %d\n", info->subdevices_avail); + kfree(info); +} + +static void snd_pcm_stream_proc_info_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer) +{ + snd_pcm_proc_info_read(((snd_pcm_str_t *)entry->private_data)->substream, buffer); +} + +static void snd_pcm_substream_proc_info_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer) +{ + snd_pcm_proc_info_read((snd_pcm_substream_t *)entry->private_data, buffer); +} + +static void snd_pcm_substream_proc_hw_params_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer) +{ + snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data; + snd_pcm_runtime_t *runtime = substream->runtime; + if (!runtime) { + snd_iprintf(buffer, "closed\n"); + return; + } + snd_pcm_stream_lock_irq(substream); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) { + snd_iprintf(buffer, "no setup\n"); + snd_pcm_stream_unlock_irq(substream); + return; + } + snd_iprintf(buffer, "access: %s\n", snd_pcm_access_name(runtime->access)); + snd_iprintf(buffer, "format: %s\n", snd_pcm_format_name(runtime->format)); + snd_iprintf(buffer, "subformat: %s\n", snd_pcm_subformat_name(runtime->subformat)); + snd_iprintf(buffer, "channels: %u\n", runtime->channels); + snd_iprintf(buffer, "rate: %u (%u/%u)\n", runtime->rate, runtime->rate_num, runtime->rate_den); + snd_iprintf(buffer, "period_size: %lu\n", runtime->period_size); + snd_iprintf(buffer, "buffer_size: %lu\n", runtime->buffer_size); + snd_iprintf(buffer, "tick_time: %u\n", runtime->tick_time); +#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) + if (substream->oss.oss) { + snd_iprintf(buffer, "OSS format: %s\n", snd_pcm_oss_format_name(runtime->oss.format)); + snd_iprintf(buffer, "OSS channels: %u\n", runtime->oss.channels); + snd_iprintf(buffer, "OSS rate: %u\n", runtime->oss.rate); + snd_iprintf(buffer, "OSS period bytes: %lu\n", (unsigned long)runtime->oss.period_bytes); + snd_iprintf(buffer, "OSS periods: %u\n", runtime->oss.periods); + snd_iprintf(buffer, "OSS period frames: %lu\n", (unsigned long)runtime->oss.period_frames); + } +#endif + snd_pcm_stream_unlock_irq(substream); +} + +static void snd_pcm_substream_proc_sw_params_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer) +{ + snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data; + snd_pcm_runtime_t *runtime = substream->runtime; + if (!runtime) { + snd_iprintf(buffer, "closed\n"); + return; + } + snd_pcm_stream_lock_irq(substream); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) { + snd_iprintf(buffer, "no setup\n"); + snd_pcm_stream_unlock_irq(substream); + return; + } + snd_iprintf(buffer, "tstamp_mode: %s\n", snd_pcm_tstamp_mode_name(runtime->tstamp_mode)); + snd_iprintf(buffer, "period_step: %u\n", runtime->period_step); + snd_iprintf(buffer, "sleep_min: %u\n", runtime->sleep_min); + snd_iprintf(buffer, "avail_min: %lu\n", runtime->control->avail_min); + snd_iprintf(buffer, "xfer_align: %lu\n", runtime->xfer_align); + snd_iprintf(buffer, "start_threshold: %lu\n", runtime->start_threshold); + snd_iprintf(buffer, "stop_threshold: %lu\n", runtime->stop_threshold); + snd_iprintf(buffer, "silence_threshold: %lu\n", runtime->silence_threshold); + snd_iprintf(buffer, "silence_size: %lu\n", runtime->silence_size); + snd_iprintf(buffer, "boundary: %lu\n", runtime->boundary); + snd_pcm_stream_unlock_irq(substream); +} + +static void snd_pcm_substream_proc_status_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer) +{ + snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data; + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_status_t status; + int err; + if (!runtime) { + snd_iprintf(buffer, "closed\n"); + return; + } + memset(&status, 0, sizeof(status)); + err = snd_pcm_status(substream, &status); + if (err < 0) { + snd_iprintf(buffer, "error %d\n", err); + return; + } + snd_iprintf(buffer, "state: %s\n", snd_pcm_state_name(status.state)); + snd_iprintf(buffer, "trigger_time: %ld.%09ld\n", + status.trigger_tstamp.tv_sec, status.trigger_tstamp.tv_nsec); + snd_iprintf(buffer, "tstamp : %ld.%09ld\n", + status.tstamp.tv_sec, status.tstamp.tv_nsec); + snd_iprintf(buffer, "delay : %ld\n", status.delay); + snd_iprintf(buffer, "avail : %ld\n", status.avail); + snd_iprintf(buffer, "avail_max : %ld\n", status.avail_max); + snd_iprintf(buffer, "-----\n"); + snd_iprintf(buffer, "hw_ptr : %ld\n", runtime->status->hw_ptr); + snd_iprintf(buffer, "appl_ptr : %ld\n", runtime->control->appl_ptr); +} +#endif + +#ifdef CONFIG_SND_DEBUG +static void snd_pcm_xrun_debug_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer) +{ + snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data; + snd_iprintf(buffer, "%d\n", pstr->xrun_debug); +} + +static void snd_pcm_xrun_debug_write(snd_info_entry_t *entry, snd_info_buffer_t *buffer) +{ + snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data; + char line[64]; + if (!snd_info_get_line(buffer, line, sizeof(line))) + pstr->xrun_debug = simple_strtoul(line, NULL, 10); +} +#endif + +static int snd_pcm_stream_proc_init(snd_pcm_str_t *pstr) +{ + snd_pcm_t *pcm = pstr->pcm; + snd_info_entry_t *entry; + char name[16]; + + sprintf(name, "pcm%i%c", pcm->device, + pstr->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c'); + if ((entry = snd_info_create_card_entry(pcm->card, name, pcm->card->proc_root)) == NULL) + return -ENOMEM; + entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + pstr->proc_root = entry; + + if ((entry = snd_info_create_card_entry(pcm->card, "info", pstr->proc_root)) != NULL) { + snd_info_set_text_ops(entry, pstr, 256, snd_pcm_stream_proc_info_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + pstr->proc_info_entry = entry; + +#ifdef CONFIG_SND_DEBUG + if ((entry = snd_info_create_card_entry(pcm->card, "xrun_debug", pstr->proc_root)) != NULL) { + entry->c.text.read_size = 64; + entry->c.text.read = snd_pcm_xrun_debug_read; + entry->c.text.write_size = 64; + entry->c.text.write = snd_pcm_xrun_debug_write; + entry->private_data = pstr; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + pstr->proc_xrun_debug_entry = entry; +#endif + return 0; +} + +static int snd_pcm_stream_proc_done(snd_pcm_str_t *pstr) +{ +#ifdef CONFIG_SND_DEBUG + if (pstr->proc_xrun_debug_entry) { + snd_info_unregister(pstr->proc_xrun_debug_entry); + pstr->proc_xrun_debug_entry = NULL; + } +#endif + if (pstr->proc_info_entry) { + snd_info_unregister(pstr->proc_info_entry); + pstr->proc_info_entry = NULL; + } + if (pstr->proc_root) { + snd_info_unregister(pstr->proc_root); + pstr->proc_root = NULL; + } + return 0; +} + +static int snd_pcm_substream_proc_init(snd_pcm_substream_t *substream) +{ + snd_info_entry_t *entry; + snd_card_t *card; + char name[16]; + + card = substream->pcm->card; + + sprintf(name, "sub%i", substream->number); + if ((entry = snd_info_create_card_entry(card, name, substream->pstr->proc_root)) == NULL) + return -ENOMEM; + entry->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + substream->proc_root = entry; + + if ((entry = snd_info_create_card_entry(card, "info", substream->proc_root)) != NULL) { + snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_info_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_info_entry = entry; + + if ((entry = snd_info_create_card_entry(card, "hw_params", substream->proc_root)) != NULL) { + snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_hw_params_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_hw_params_entry = entry; + + if ((entry = snd_info_create_card_entry(card, "sw_params", substream->proc_root)) != NULL) { + snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_sw_params_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_sw_params_entry = entry; + + if ((entry = snd_info_create_card_entry(card, "status", substream->proc_root)) != NULL) { + snd_info_set_text_ops(entry, substream, 256, snd_pcm_substream_proc_status_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_status_entry = entry; + + return 0; +} + +static int snd_pcm_substream_proc_done(snd_pcm_substream_t *substream) +{ + if (substream->proc_info_entry) { + snd_info_unregister(substream->proc_info_entry); + substream->proc_info_entry = NULL; + } + if (substream->proc_hw_params_entry) { + snd_info_unregister(substream->proc_hw_params_entry); + substream->proc_hw_params_entry = NULL; + } + if (substream->proc_sw_params_entry) { + snd_info_unregister(substream->proc_sw_params_entry); + substream->proc_sw_params_entry = NULL; + } + if (substream->proc_status_entry) { + snd_info_unregister(substream->proc_status_entry); + substream->proc_status_entry = NULL; + } + if (substream->proc_root) { + snd_info_unregister(substream->proc_root); + substream->proc_root = NULL; + } + return 0; +} + +/** + * snd_pcm_new_stream - create a new PCM stream + * @pcm: the pcm instance + * @stream: the stream direction, SNDRV_PCM_STREAM_XXX + * @substream_count: the number of substreams + * + * Creates a new stream for the pcm. + * The corresponding stream on the pcm must have been empty before + * calling this, i.e. zero must be given to the argument of + * snd_pcm_new(). + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_new_stream(snd_pcm_t *pcm, int stream, int substream_count) +{ + int idx, err; + snd_pcm_str_t *pstr = &pcm->streams[stream]; + snd_pcm_substream_t *substream, *prev; + +#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) + init_MUTEX(&pstr->oss.setup_mutex); +#endif + pstr->stream = stream; + pstr->pcm = pcm; + pstr->substream_count = substream_count; + pstr->reg = &snd_pcm_reg[stream]; + if (substream_count > 0) { + err = snd_pcm_stream_proc_init(pstr); + if (err < 0) + return err; + } + prev = NULL; + for (idx = 0, prev = NULL; idx < substream_count; idx++) { + substream = kcalloc(1, sizeof(*substream), GFP_KERNEL); + if (substream == NULL) + return -ENOMEM; + substream->pcm = pcm; + substream->pstr = pstr; + substream->number = idx; + substream->stream = stream; + sprintf(substream->name, "subdevice #%i", idx); + substream->buffer_bytes_max = UINT_MAX; + if (prev == NULL) + pstr->substream = substream; + else + prev->next = substream; + err = snd_pcm_substream_proc_init(substream); + if (err < 0) { + kfree(substream); + return err; + } + substream->group = &substream->self_group; + spin_lock_init(&substream->self_group.lock); + INIT_LIST_HEAD(&substream->self_group.substreams); + list_add_tail(&substream->link_list, &substream->self_group.substreams); + spin_lock_init(&substream->timer_lock); + prev = substream; + } + return 0; +} + +/** + * snd_pcm_new - create a new PCM instance + * @card: the card instance + * @id: the id string + * @device: the device index (zero based) + * @playback_count: the number of substreams for playback + * @capture_count: the number of substreams for capture + * @rpcm: the pointer to store the new pcm instance + * + * Creates a new PCM instance. + * + * The pcm operators have to be set afterwards to the new instance + * via snd_pcm_set_ops(). + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_new(snd_card_t * card, char *id, int device, + int playback_count, int capture_count, + snd_pcm_t ** rpcm) +{ + snd_pcm_t *pcm; + int err; + static snd_device_ops_t ops = { + .dev_free = snd_pcm_dev_free, + .dev_register = snd_pcm_dev_register, + .dev_disconnect = snd_pcm_dev_disconnect, + .dev_unregister = snd_pcm_dev_unregister + }; + + snd_assert(rpcm != NULL, return -EINVAL); + *rpcm = NULL; + snd_assert(card != NULL, return -ENXIO); + pcm = kcalloc(1, sizeof(*pcm), GFP_KERNEL); + if (pcm == NULL) + return -ENOMEM; + pcm->card = card; + pcm->device = device; + if (id) { + strlcpy(pcm->id, id, sizeof(pcm->id)); + } + if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) { + snd_pcm_free(pcm); + return err; + } + if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) { + snd_pcm_free(pcm); + return err; + } + init_MUTEX(&pcm->open_mutex); + init_waitqueue_head(&pcm->open_wait); + if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) { + snd_pcm_free(pcm); + return err; + } + *rpcm = pcm; + return 0; +} + +static void snd_pcm_free_stream(snd_pcm_str_t * pstr) +{ + snd_pcm_substream_t *substream, *substream_next; +#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) + snd_pcm_oss_setup_t *setup, *setupn; +#endif + substream = pstr->substream; + while (substream) { + substream_next = substream->next; + snd_pcm_substream_proc_done(substream); + kfree(substream); + substream = substream_next; + } + snd_pcm_stream_proc_done(pstr); +#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) + for (setup = pstr->oss.setup_list; setup; setup = setupn) { + setupn = setup->next; + kfree(setup->task_name); + kfree(setup); + } +#endif +} + +static int snd_pcm_free(snd_pcm_t *pcm) +{ + snd_assert(pcm != NULL, return -ENXIO); + if (pcm->private_free) + pcm->private_free(pcm); + snd_pcm_lib_preallocate_free_for_all(pcm); + snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]); + snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_CAPTURE]); + kfree(pcm); + return 0; +} + +static int snd_pcm_dev_free(snd_device_t *device) +{ + snd_pcm_t *pcm = device->device_data; + return snd_pcm_free(pcm); +} + +static void snd_pcm_tick_timer_func(unsigned long data) +{ + snd_pcm_substream_t *substream = (snd_pcm_substream_t*) data; + snd_pcm_tick_elapsed(substream); +} + +int snd_pcm_open_substream(snd_pcm_t *pcm, int stream, + snd_pcm_substream_t **rsubstream) +{ + snd_pcm_str_t * pstr; + snd_pcm_substream_t * substream; + snd_pcm_runtime_t * runtime; + snd_ctl_file_t *kctl; + snd_card_t *card; + struct list_head *list; + int prefer_subdevice = -1; + size_t size; + + snd_assert(rsubstream != NULL, return -EINVAL); + *rsubstream = NULL; + snd_assert(pcm != NULL, return -ENXIO); + pstr = &pcm->streams[stream]; + if (pstr->substream == NULL) + return -ENODEV; + + card = pcm->card; + down_read(&card->controls_rwsem); + list_for_each(list, &card->ctl_files) { + kctl = snd_ctl_file(list); + if (kctl->pid == current->pid) { + prefer_subdevice = kctl->prefer_pcm_subdevice; + break; + } + } + up_read(&card->controls_rwsem); + + if (pstr->substream_count == 0) + return -ENODEV; + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) { + for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next) { + if (SUBSTREAM_BUSY(substream)) + return -EAGAIN; + } + } + break; + case SNDRV_PCM_STREAM_CAPTURE: + if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) { + for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) { + if (SUBSTREAM_BUSY(substream)) + return -EAGAIN; + } + } + break; + default: + return -EINVAL; + } + + if (prefer_subdevice >= 0) { + for (substream = pstr->substream; substream; substream = substream->next) + if (!SUBSTREAM_BUSY(substream) && substream->number == prefer_subdevice) + goto __ok; + } + for (substream = pstr->substream; substream; substream = substream->next) + if (!SUBSTREAM_BUSY(substream)) + break; + __ok: + if (substream == NULL) + return -EAGAIN; + + runtime = kcalloc(1, sizeof(*runtime), GFP_KERNEL); + if (runtime == NULL) + return -ENOMEM; + + size = PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t)); + runtime->status = snd_malloc_pages(size, GFP_KERNEL); + if (runtime->status == NULL) { + kfree(runtime); + return -ENOMEM; + } + memset((void*)runtime->status, 0, size); + + size = PAGE_ALIGN(sizeof(snd_pcm_mmap_control_t)); + runtime->control = snd_malloc_pages(size, GFP_KERNEL); + if (runtime->control == NULL) { + snd_free_pages((void*)runtime->status, PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t))); + kfree(runtime); + return -ENOMEM; + } + memset((void*)runtime->control, 0, size); + + init_waitqueue_head(&runtime->sleep); + atomic_set(&runtime->mmap_count, 0); + init_timer(&runtime->tick_timer); + runtime->tick_timer.function = snd_pcm_tick_timer_func; + runtime->tick_timer.data = (unsigned long) substream; + + runtime->status->state = SNDRV_PCM_STATE_OPEN; + + substream->runtime = runtime; + substream->private_data = pcm->private_data; + pstr->substream_opened++; + *rsubstream = substream; + return 0; +} + +void snd_pcm_release_substream(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t * runtime; + substream->file = NULL; + runtime = substream->runtime; + snd_assert(runtime != NULL, return); + if (runtime->private_free != NULL) + runtime->private_free(runtime); + snd_free_pages((void*)runtime->status, PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t))); + snd_free_pages((void*)runtime->control, PAGE_ALIGN(sizeof(snd_pcm_mmap_control_t))); + kfree(runtime->hw_constraints.rules); + kfree(runtime); + substream->runtime = NULL; + substream->pstr->substream_opened--; +} + +static int snd_pcm_dev_register(snd_device_t *device) +{ + int idx, cidx, err; + unsigned short minor; + snd_pcm_substream_t *substream; + struct list_head *list; + char str[16]; + snd_pcm_t *pcm = device->device_data; + + snd_assert(pcm != NULL && device != NULL, return -ENXIO); + down(®ister_mutex); + idx = (pcm->card->number * SNDRV_PCM_DEVICES) + pcm->device; + if (snd_pcm_devices[idx]) { + up(®ister_mutex); + return -EBUSY; + } + snd_pcm_devices[idx] = pcm; + for (cidx = 0; cidx < 2; cidx++) { + int devtype = -1; + if (pcm->streams[cidx].substream == NULL) + continue; + switch (cidx) { + case SNDRV_PCM_STREAM_PLAYBACK: + sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device); + minor = SNDRV_MINOR_PCM_PLAYBACK + idx; + devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK; + break; + case SNDRV_PCM_STREAM_CAPTURE: + sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device); + minor = SNDRV_MINOR_PCM_CAPTURE + idx; + devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE; + break; + } + if ((err = snd_register_device(devtype, pcm->card, pcm->device, pcm->streams[cidx].reg, str)) < 0) { + snd_pcm_devices[idx] = NULL; + up(®ister_mutex); + return err; + } + for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) + snd_pcm_timer_init(substream); + } + list_for_each(list, &snd_pcm_notify_list) { + snd_pcm_notify_t *notify; + notify = list_entry(list, snd_pcm_notify_t, list); + notify->n_register(pcm); + } + up(®ister_mutex); + return 0; +} + +static int snd_pcm_dev_disconnect(snd_device_t *device) +{ + snd_pcm_t *pcm = device->device_data; + struct list_head *list; + snd_pcm_substream_t *substream; + int idx, cidx; + + down(®ister_mutex); + idx = (pcm->card->number * SNDRV_PCM_DEVICES) + pcm->device; + snd_pcm_devices[idx] = NULL; + for (cidx = 0; cidx < 2; cidx++) + for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) + if (substream->runtime) + substream->runtime->status->state = SNDRV_PCM_STATE_DISCONNECTED; + list_for_each(list, &snd_pcm_notify_list) { + snd_pcm_notify_t *notify; + notify = list_entry(list, snd_pcm_notify_t, list); + notify->n_disconnect(pcm); + } + up(®ister_mutex); + return 0; +} + +static int snd_pcm_dev_unregister(snd_device_t *device) +{ + int idx, cidx, devtype; + snd_pcm_substream_t *substream; + struct list_head *list; + snd_pcm_t *pcm = device->device_data; + + snd_assert(pcm != NULL, return -ENXIO); + down(®ister_mutex); + idx = (pcm->card->number * SNDRV_PCM_DEVICES) + pcm->device; + snd_pcm_devices[idx] = NULL; + for (cidx = 0; cidx < 2; cidx++) { + devtype = -1; + switch (cidx) { + case SNDRV_PCM_STREAM_PLAYBACK: + devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK; + break; + case SNDRV_PCM_STREAM_CAPTURE: + devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE; + break; + } + snd_unregister_device(devtype, pcm->card, pcm->device); + for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) + snd_pcm_timer_done(substream); + } + list_for_each(list, &snd_pcm_notify_list) { + snd_pcm_notify_t *notify; + notify = list_entry(list, snd_pcm_notify_t, list); + notify->n_unregister(pcm); + } + up(®ister_mutex); + return snd_pcm_free(pcm); +} + +int snd_pcm_notify(snd_pcm_notify_t *notify, int nfree) +{ + int idx; + + snd_assert(notify != NULL && notify->n_register != NULL && notify->n_unregister != NULL, return -EINVAL); + down(®ister_mutex); + if (nfree) { + list_del(¬ify->list); + for (idx = 0; idx < SNDRV_CARDS * SNDRV_PCM_DEVICES; idx++) { + if (snd_pcm_devices[idx] == NULL) + continue; + notify->n_unregister(snd_pcm_devices[idx]); + } + } else { + list_add_tail(¬ify->list, &snd_pcm_notify_list); + for (idx = 0; idx < SNDRV_CARDS * SNDRV_PCM_DEVICES; idx++) { + if (snd_pcm_devices[idx] == NULL) + continue; + notify->n_register(snd_pcm_devices[idx]); + } + } + up(®ister_mutex); + return 0; +} + +/* + * Info interface + */ + +static void snd_pcm_proc_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + int idx; + snd_pcm_t *pcm; + + down(®ister_mutex); + for (idx = 0; idx < SNDRV_CARDS * SNDRV_PCM_DEVICES; idx++) { + pcm = snd_pcm_devices[idx]; + if (pcm == NULL) + continue; + snd_iprintf(buffer, "%02i-%02i: %s : %s", idx / SNDRV_PCM_DEVICES, + idx % SNDRV_PCM_DEVICES, pcm->id, pcm->name); + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) + snd_iprintf(buffer, " : playback %i", pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count); + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) + snd_iprintf(buffer, " : capture %i", pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count); + snd_iprintf(buffer, "\n"); + } + up(®ister_mutex); +} + +/* + * ENTRY functions + */ + +static snd_info_entry_t *snd_pcm_proc_entry = NULL; + +static int __init alsa_pcm_init(void) +{ + snd_info_entry_t *entry; + + snd_ctl_register_ioctl(snd_pcm_control_ioctl); + snd_ctl_register_ioctl_compat(snd_pcm_control_ioctl); + if ((entry = snd_info_create_module_entry(THIS_MODULE, "pcm", NULL)) != NULL) { + snd_info_set_text_ops(entry, NULL, SNDRV_CARDS * SNDRV_PCM_DEVICES * 128, snd_pcm_proc_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_pcm_proc_entry = entry; + return 0; +} + +static void __exit alsa_pcm_exit(void) +{ + snd_ctl_unregister_ioctl(snd_pcm_control_ioctl); + snd_ctl_unregister_ioctl_compat(snd_pcm_control_ioctl); + if (snd_pcm_proc_entry) { + snd_info_unregister(snd_pcm_proc_entry); + snd_pcm_proc_entry = NULL; + } +} + +module_init(alsa_pcm_init) +module_exit(alsa_pcm_exit) + +EXPORT_SYMBOL(snd_pcm_devices); +EXPORT_SYMBOL(snd_pcm_new); +EXPORT_SYMBOL(snd_pcm_new_stream); +EXPORT_SYMBOL(snd_pcm_notify); +EXPORT_SYMBOL(snd_pcm_open_substream); +EXPORT_SYMBOL(snd_pcm_release_substream); +EXPORT_SYMBOL(snd_pcm_format_name); + /* pcm_native.c */ +EXPORT_SYMBOL(snd_pcm_link_rwlock); +EXPORT_SYMBOL(snd_pcm_start); +#ifdef CONFIG_PM +EXPORT_SYMBOL(snd_pcm_suspend); +EXPORT_SYMBOL(snd_pcm_suspend_all); +#endif +EXPORT_SYMBOL(snd_pcm_kernel_playback_ioctl); +EXPORT_SYMBOL(snd_pcm_kernel_capture_ioctl); +EXPORT_SYMBOL(snd_pcm_kernel_ioctl); +EXPORT_SYMBOL(snd_pcm_mmap_data); +#if SNDRV_PCM_INFO_MMAP_IOMEM +EXPORT_SYMBOL(snd_pcm_lib_mmap_iomem); +#endif + /* pcm_misc.c */ +EXPORT_SYMBOL(snd_pcm_format_signed); +EXPORT_SYMBOL(snd_pcm_format_unsigned); +EXPORT_SYMBOL(snd_pcm_format_linear); +EXPORT_SYMBOL(snd_pcm_format_little_endian); +EXPORT_SYMBOL(snd_pcm_format_big_endian); +EXPORT_SYMBOL(snd_pcm_format_width); +EXPORT_SYMBOL(snd_pcm_format_physical_width); +EXPORT_SYMBOL(snd_pcm_format_silence_64); +EXPORT_SYMBOL(snd_pcm_format_set_silence); +EXPORT_SYMBOL(snd_pcm_build_linear_format); +EXPORT_SYMBOL(snd_pcm_limit_hw_rates); diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c new file mode 100644 index 000000000000..3920bf0eebbf --- /dev/null +++ b/sound/core/pcm_compat.c @@ -0,0 +1,513 @@ +/* + * 32bit -> 64bit ioctl wrapper for PCM API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This file included from pcm_native.c */ + +#include <linux/compat.h> + +static int snd_pcm_ioctl_delay_compat(snd_pcm_substream_t *substream, + s32 __user *src) +{ + snd_pcm_sframes_t delay; + mm_segment_t fs; + int err; + + fs = snd_enter_user(); + err = snd_pcm_delay(substream, &delay); + snd_leave_user(fs); + if (err < 0) + return err; + if (put_user(delay, src)) + return -EFAULT; + return err; +} + +static int snd_pcm_ioctl_rewind_compat(snd_pcm_substream_t *substream, + u32 __user *src) +{ + snd_pcm_uframes_t frames; + int err; + + if (get_user(frames, src)) + return -EFAULT; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + err = snd_pcm_playback_rewind(substream, frames); + else + err = snd_pcm_capture_rewind(substream, frames); + if (put_user(err, src)) + return -EFAULT; + return err < 0 ? err : 0; +} + +static int snd_pcm_ioctl_forward_compat(snd_pcm_substream_t *substream, + u32 __user *src) +{ + snd_pcm_uframes_t frames; + int err; + + if (get_user(frames, src)) + return -EFAULT; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + err = snd_pcm_playback_forward(substream, frames); + else + err = snd_pcm_capture_forward(substream, frames); + if (put_user(err, src)) + return -EFAULT; + return err < 0 ? err : 0; +} + +struct sndrv_pcm_hw_params32 { + u32 flags; + struct sndrv_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; /* this must be identical */ + struct sndrv_mask mres[5]; /* reserved masks */ + struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1]; + struct sndrv_interval ires[9]; /* reserved intervals */ + u32 rmask; + u32 cmask; + u32 info; + u32 msbits; + u32 rate_num; + u32 rate_den; + u32 fifo_size; + unsigned char reserved[64]; +}; + +struct sndrv_pcm_sw_params32 { + s32 tstamp_mode; + u32 period_step; + u32 sleep_min; + u32 avail_min; + u32 xfer_align; + u32 start_threshold; + u32 stop_threshold; + u32 silence_threshold; + u32 silence_size; + u32 boundary; + unsigned char reserved[64]; +}; + +static int snd_pcm_ioctl_sw_params_compat(snd_pcm_substream_t *substream, + struct sndrv_pcm_sw_params32 __user *src) +{ + snd_pcm_sw_params_t params; + int err; + + memset(¶ms, 0, sizeof(params)); + if (get_user(params.tstamp_mode, &src->tstamp_mode) || + get_user(params.period_step, &src->period_step) || + get_user(params.sleep_min, &src->sleep_min) || + get_user(params.avail_min, &src->avail_min) || + get_user(params.xfer_align, &src->xfer_align) || + get_user(params.start_threshold, &src->start_threshold) || + get_user(params.stop_threshold, &src->stop_threshold) || + get_user(params.silence_threshold, &src->silence_threshold) || + get_user(params.silence_size, &src->silence_size)) + return -EFAULT; + err = snd_pcm_sw_params(substream, ¶ms); + if (err < 0) + return err; + if (put_user(params.boundary, &src->boundary)) + return -EFAULT; + return err; +} + +struct sndrv_pcm_channel_info32 { + u32 channel; + u32 offset; + u32 first; + u32 step; +}; + +static int snd_pcm_ioctl_channel_info_compat(snd_pcm_substream_t *substream, + struct sndrv_pcm_channel_info32 __user *src) +{ + snd_pcm_channel_info_t info; + int err; + + if (get_user(info.channel, &src->channel) || + get_user(info.offset, &src->offset) || + get_user(info.first, &src->first) || + get_user(info.step, &src->step)) + return -EFAULT; + err = snd_pcm_channel_info(substream, &info); + if (err < 0) + return err; + if (put_user(info.channel, &src->channel) || + put_user(info.offset, &src->offset) || + put_user(info.first, &src->first) || + put_user(info.step, &src->step)) + return -EFAULT; + return err; +} + +struct sndrv_pcm_status32 { + s32 state; + struct compat_timespec trigger_tstamp; + struct compat_timespec tstamp; + u32 appl_ptr; + u32 hw_ptr; + s32 delay; + u32 avail; + u32 avail_max; + u32 overrange; + s32 suspended_state; + unsigned char reserved[60]; +} __attribute__((packed)); + + +static int snd_pcm_status_user_compat(snd_pcm_substream_t *substream, + struct sndrv_pcm_status32 __user *src) +{ + snd_pcm_status_t status; + int err; + + err = snd_pcm_status(substream, &status); + if (err < 0) + return err; + + if (put_user(status.state, &src->state) || + put_user(status.trigger_tstamp.tv_sec, &src->trigger_tstamp.tv_sec) || + put_user(status.trigger_tstamp.tv_nsec, &src->trigger_tstamp.tv_nsec) || + put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) || + put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) || + put_user(status.appl_ptr, &src->appl_ptr) || + put_user(status.hw_ptr, &src->hw_ptr) || + put_user(status.delay, &src->delay) || + put_user(status.avail, &src->avail) || + put_user(status.avail_max, &src->avail_max) || + put_user(status.overrange, &src->overrange) || + put_user(status.suspended_state, &src->suspended_state)) + return -EFAULT; + + return err; +} + +/* recalcuate the boundary within 32bit */ +static void recalculate_boundary(snd_pcm_runtime_t *runtime) +{ + if (! runtime->buffer_size) + return; + runtime->boundary = runtime->buffer_size; + while (runtime->boundary * 2 <= 0x7fffffffUL - runtime->buffer_size) + runtime->boundary *= 2; +} + +/* both for HW_PARAMS and HW_REFINE */ +static int snd_pcm_ioctl_hw_params_compat(snd_pcm_substream_t *substream, + int refine, + struct sndrv_pcm_hw_params32 __user *data32) +{ + struct sndrv_pcm_hw_params *data; + snd_pcm_runtime_t *runtime; + int err; + + if (! (runtime = substream->runtime)) + return -ENOTTY; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + /* only fifo_size is different, so just copy all */ + if (copy_from_user(data, data32, sizeof(*data32))) { + err = -EFAULT; + goto error; + } + if (refine) + err = snd_pcm_hw_refine(substream, data); + else + err = snd_pcm_hw_params(substream, data); + if (err < 0) + goto error; + if (copy_to_user(data32, data, sizeof(*data32)) || + put_user(data->fifo_size, &data32->fifo_size)) { + err = -EFAULT; + goto error; + } + + if (! refine) + recalculate_boundary(runtime); + error: + kfree(data); + return err; +} + + +/* + */ +struct sndrv_xferi32 { + s32 result; + u32 buf; + u32 frames; +}; + +static int snd_pcm_ioctl_xferi_compat(snd_pcm_substream_t *substream, + int dir, struct sndrv_xferi32 __user *data32) +{ + compat_caddr_t buf; + u32 frames; + int err; + + if (! substream->runtime) + return -ENOTTY; + if (substream->stream != dir) + return -EINVAL; + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + if (get_user(buf, &data32->buf) || + get_user(frames, &data32->frames)) + return -EFAULT; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + err = snd_pcm_lib_write(substream, compat_ptr(buf), frames); + else + err = snd_pcm_lib_read(substream, compat_ptr(buf), frames); + if (err < 0) + return err; + /* copy the result */ + if (put_user(err, &data32->result)) + return -EFAULT; + return 0; +} + + +/* snd_xfern needs remapping of bufs */ +struct sndrv_xfern32 { + s32 result; + u32 bufs; /* this is void **; */ + u32 frames; +}; + +/* + * xfern ioctl nees to copy (up to) 128 pointers on stack. + * although we may pass the copied pointers through f_op->ioctl, but the ioctl + * handler there expands again the same 128 pointers on stack, so it is better + * to handle the function (calling pcm_readv/writev) directly in this handler. + */ +static int snd_pcm_ioctl_xfern_compat(snd_pcm_substream_t *substream, + int dir, struct sndrv_xfern32 __user *data32) +{ + compat_caddr_t buf; + compat_caddr_t __user *bufptr; + u32 frames; + void __user **bufs; + int err, ch, i; + + if (! substream->runtime) + return -ENOTTY; + if (substream->stream != dir) + return -EINVAL; + + if ((ch = substream->runtime->channels) > 128) + return -EINVAL; + if (get_user(buf, &data32->bufs) || + get_user(frames, &data32->frames)) + return -EFAULT; + bufptr = compat_ptr(buf); + bufs = kmalloc(sizeof(void __user *) * ch, GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + for (i = 0; i < ch; i++) { + u32 ptr; + if (get_user(ptr, bufptr)) { + kfree(bufs); + return -EFAULT; + } + bufs[ch] = compat_ptr(ptr); + bufptr++; + } + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + err = snd_pcm_lib_writev(substream, bufs, frames); + else + err = snd_pcm_lib_readv(substream, bufs, frames); + if (err >= 0) { + if (put_user(err, &data32->result)) + err = -EFAULT; + } + kfree(bufs); + return err; +} + + +struct sndrv_pcm_mmap_status32 { + s32 state; + s32 pad1; + u32 hw_ptr; + struct compat_timespec tstamp; + s32 suspended_state; +} __attribute__((packed)); + +struct sndrv_pcm_mmap_control32 { + u32 appl_ptr; + u32 avail_min; +}; + +struct sndrv_pcm_sync_ptr32 { + u32 flags; + union { + struct sndrv_pcm_mmap_status32 status; + unsigned char reserved[64]; + } s; + union { + struct sndrv_pcm_mmap_control32 control; + unsigned char reserved[64]; + } c; +} __attribute__((packed)); + +static int snd_pcm_ioctl_sync_ptr_compat(snd_pcm_substream_t *substream, + struct sndrv_pcm_sync_ptr32 __user *src) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + volatile struct sndrv_pcm_mmap_status *status; + volatile struct sndrv_pcm_mmap_control *control; + u32 sflags; + struct sndrv_pcm_mmap_control scontrol; + struct sndrv_pcm_mmap_status sstatus; + int err; + + snd_assert(runtime, return -EINVAL); + + if (get_user(sflags, &src->flags) || + get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || + get_user(scontrol.avail_min, &src->c.control.avail_min)) + return -EFAULT; + if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) { + err = snd_pcm_hwsync(substream); + if (err < 0) + return err; + } + status = runtime->status; + control = runtime->control; + snd_pcm_stream_lock_irq(substream); + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) + control->appl_ptr = scontrol.appl_ptr; + else + scontrol.appl_ptr = control->appl_ptr; + if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) + control->avail_min = scontrol.avail_min; + else + scontrol.avail_min = control->avail_min; + sstatus.state = status->state; + sstatus.hw_ptr = status->hw_ptr; + sstatus.tstamp = status->tstamp; + sstatus.suspended_state = status->suspended_state; + snd_pcm_stream_unlock_irq(substream); + if (put_user(sstatus.state, &src->s.status.state) || + put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || + put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp.tv_sec) || + put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp.tv_nsec) || + put_user(sstatus.suspended_state, &src->s.status.suspended_state) || + put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || + put_user(scontrol.avail_min, &src->c.control.avail_min)) + return -EFAULT; + + return 0; +} + + +/* + */ +enum { + SNDRV_PCM_IOCTL_HW_REFINE32 = _IOWR('A', 0x10, struct sndrv_pcm_hw_params32), + SNDRV_PCM_IOCTL_HW_PARAMS32 = _IOWR('A', 0x11, struct sndrv_pcm_hw_params32), + SNDRV_PCM_IOCTL_SW_PARAMS32 = _IOWR('A', 0x13, struct sndrv_pcm_sw_params32), + SNDRV_PCM_IOCTL_STATUS32 = _IOR('A', 0x20, struct sndrv_pcm_status32), + SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32), + SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct sndrv_pcm_channel_info32), + SNDRV_PCM_IOCTL_REWIND32 = _IOW('A', 0x46, u32), + SNDRV_PCM_IOCTL_FORWARD32 = _IOW('A', 0x49, u32), + SNDRV_PCM_IOCTL_WRITEI_FRAMES32 = _IOW('A', 0x50, struct sndrv_xferi32), + SNDRV_PCM_IOCTL_READI_FRAMES32 = _IOR('A', 0x51, struct sndrv_xferi32), + SNDRV_PCM_IOCTL_WRITEN_FRAMES32 = _IOW('A', 0x52, struct sndrv_xfern32), + SNDRV_PCM_IOCTL_READN_FRAMES32 = _IOR('A', 0x53, struct sndrv_xfern32), + SNDRV_PCM_IOCTL_SYNC_PTR32 = _IOWR('A', 0x23, struct sndrv_pcm_sync_ptr32), + +}; + +static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_pcm_file_t *pcm_file; + snd_pcm_substream_t *substream; + void __user *argp = compat_ptr(arg); + + pcm_file = file->private_data; + if (! pcm_file) + return -ENOTTY; + substream = pcm_file->substream; + if (! substream) + return -ENOTTY; + + /* + * When PCM is used on 32bit mode, we need to disable + * mmap of PCM status/control records because of the size + * incompatibility. + */ + substream->no_mmap_ctrl = 1; + + switch (cmd) { + case SNDRV_PCM_IOCTL_PVERSION: + case SNDRV_PCM_IOCTL_INFO: + case SNDRV_PCM_IOCTL_TSTAMP: + case SNDRV_PCM_IOCTL_HWSYNC: + case SNDRV_PCM_IOCTL_PREPARE: + case SNDRV_PCM_IOCTL_RESET: + case SNDRV_PCM_IOCTL_START: + case SNDRV_PCM_IOCTL_DROP: + case SNDRV_PCM_IOCTL_DRAIN: + case SNDRV_PCM_IOCTL_PAUSE: + case SNDRV_PCM_IOCTL_HW_FREE: + case SNDRV_PCM_IOCTL_RESUME: + case SNDRV_PCM_IOCTL_XRUN: + case SNDRV_PCM_IOCTL_LINK: + case SNDRV_PCM_IOCTL_UNLINK: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return snd_pcm_playback_ioctl1(substream, cmd, argp); + else + return snd_pcm_capture_ioctl1(substream, cmd, argp); + case SNDRV_PCM_IOCTL_HW_REFINE32: + return snd_pcm_ioctl_hw_params_compat(substream, 1, argp); + case SNDRV_PCM_IOCTL_HW_PARAMS32: + return snd_pcm_ioctl_hw_params_compat(substream, 0, argp); + case SNDRV_PCM_IOCTL_SW_PARAMS32: + return snd_pcm_ioctl_sw_params_compat(substream, argp); + case SNDRV_PCM_IOCTL_STATUS32: + return snd_pcm_status_user_compat(substream, argp); + case SNDRV_PCM_IOCTL_SYNC_PTR32: + return snd_pcm_ioctl_sync_ptr_compat(substream, argp); + case SNDRV_PCM_IOCTL_CHANNEL_INFO32: + return snd_pcm_ioctl_channel_info_compat(substream, argp); + case SNDRV_PCM_IOCTL_WRITEI_FRAMES32: + return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp); + case SNDRV_PCM_IOCTL_READI_FRAMES32: + return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp); + case SNDRV_PCM_IOCTL_WRITEN_FRAMES32: + return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp); + case SNDRV_PCM_IOCTL_READN_FRAMES32: + return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp); + case SNDRV_PCM_IOCTL_DELAY32: + return snd_pcm_ioctl_delay_compat(substream, argp); + case SNDRV_PCM_IOCTL_REWIND32: + return snd_pcm_ioctl_rewind_compat(substream, argp); + case SNDRV_PCM_IOCTL_FORWARD32: + return snd_pcm_ioctl_forward_compat(substream, argp); + } + + return -ENOIOCTLCMD; +} diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c new file mode 100644 index 000000000000..151fd99ca2c9 --- /dev/null +++ b/sound/core/pcm_lib.c @@ -0,0 +1,2612 @@ +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Abramo Bagnara <abramo@alsa-project.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/timer.h> + +/* + * fill ring buffer with silence + * runtime->silence_start: starting pointer to silence area + * runtime->silence_filled: size filled with silence + * runtime->silence_threshold: threshold from application + * runtime->silence_size: maximal size from application + * + * when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately + */ +void snd_pcm_playback_silence(snd_pcm_substream_t *substream, snd_pcm_uframes_t new_hw_ptr) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_uframes_t frames, ofs, transfer; + + if (runtime->silence_size < runtime->boundary) { + snd_pcm_sframes_t noise_dist, n; + if (runtime->silence_start != runtime->control->appl_ptr) { + n = runtime->control->appl_ptr - runtime->silence_start; + if (n < 0) + n += runtime->boundary; + if ((snd_pcm_uframes_t)n < runtime->silence_filled) + runtime->silence_filled -= n; + else + runtime->silence_filled = 0; + runtime->silence_start = runtime->control->appl_ptr; + } + if (runtime->silence_filled == runtime->buffer_size) + return; + snd_assert(runtime->silence_filled <= runtime->buffer_size, return); + noise_dist = snd_pcm_playback_hw_avail(runtime) + runtime->silence_filled; + if (noise_dist >= (snd_pcm_sframes_t) runtime->silence_threshold) + return; + frames = runtime->silence_threshold - noise_dist; + if (frames > runtime->silence_size) + frames = runtime->silence_size; + } else { + if (new_hw_ptr == ULONG_MAX) { /* initialization */ + snd_pcm_sframes_t avail = snd_pcm_playback_hw_avail(runtime); + runtime->silence_filled = avail > 0 ? avail : 0; + runtime->silence_start = (runtime->status->hw_ptr + + runtime->silence_filled) % + runtime->boundary; + } else { + ofs = runtime->status->hw_ptr; + frames = new_hw_ptr - ofs; + if ((snd_pcm_sframes_t)frames < 0) + frames += runtime->boundary; + runtime->silence_filled -= frames; + if ((snd_pcm_sframes_t)runtime->silence_filled < 0) { + runtime->silence_filled = 0; + runtime->silence_start = (ofs + frames) - runtime->buffer_size; + } else { + runtime->silence_start = ofs - runtime->silence_filled; + } + if ((snd_pcm_sframes_t)runtime->silence_start < 0) + runtime->silence_start += runtime->boundary; + } + frames = runtime->buffer_size - runtime->silence_filled; + } + snd_assert(frames <= runtime->buffer_size, return); + if (frames == 0) + return; + ofs = (runtime->silence_start + runtime->silence_filled) % runtime->buffer_size; + while (frames > 0) { + transfer = ofs + frames > runtime->buffer_size ? runtime->buffer_size - ofs : frames; + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) { + if (substream->ops->silence) { + int err; + err = substream->ops->silence(substream, -1, ofs, transfer); + snd_assert(err >= 0, ); + } else { + char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, ofs); + snd_pcm_format_set_silence(runtime->format, hwbuf, transfer * runtime->channels); + } + } else { + unsigned int c; + unsigned int channels = runtime->channels; + if (substream->ops->silence) { + for (c = 0; c < channels; ++c) { + int err; + err = substream->ops->silence(substream, c, ofs, transfer); + snd_assert(err >= 0, ); + } + } else { + size_t dma_csize = runtime->dma_bytes / channels; + for (c = 0; c < channels; ++c) { + char *hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, ofs); + snd_pcm_format_set_silence(runtime->format, hwbuf, transfer); + } + } + } + runtime->silence_filled += transfer; + frames -= transfer; + ofs = 0; + } +} + +static void xrun(snd_pcm_substream_t *substream) +{ + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); +#ifdef CONFIG_SND_DEBUG + if (substream->pstr->xrun_debug) { + snd_printd(KERN_DEBUG "XRUN: pcmC%dD%d%c\n", + substream->pcm->card->number, + substream->pcm->device, + substream->stream ? 'c' : 'p'); + if (substream->pstr->xrun_debug > 1) + dump_stack(); + } +#endif +} + +static inline snd_pcm_uframes_t snd_pcm_update_hw_ptr_pos(snd_pcm_substream_t *substream, + snd_pcm_runtime_t *runtime) +{ + snd_pcm_uframes_t pos; + + pos = substream->ops->pointer(substream); + if (pos == SNDRV_PCM_POS_XRUN) + return pos; /* XRUN */ + if (runtime->tstamp_mode & SNDRV_PCM_TSTAMP_MMAP) + snd_timestamp_now((snd_timestamp_t*)&runtime->status->tstamp, runtime->tstamp_timespec); +#ifdef CONFIG_SND_DEBUG + if (pos >= runtime->buffer_size) { + snd_printk(KERN_ERR "BUG: stream = %i, pos = 0x%lx, buffer size = 0x%lx, period size = 0x%lx\n", substream->stream, pos, runtime->buffer_size, runtime->period_size); + } else +#endif + snd_runtime_check(pos < runtime->buffer_size, return 0); + pos -= pos % runtime->min_align; + return pos; +} + +static inline int snd_pcm_update_hw_ptr_post(snd_pcm_substream_t *substream, + snd_pcm_runtime_t *runtime) +{ + snd_pcm_uframes_t avail; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + avail = snd_pcm_playback_avail(runtime); + else + avail = snd_pcm_capture_avail(runtime); + if (avail > runtime->avail_max) + runtime->avail_max = avail; + if (avail >= runtime->stop_threshold) { + if (substream->runtime->status->state == SNDRV_PCM_STATE_DRAINING) + snd_pcm_drain_done(substream); + else + xrun(substream); + return -EPIPE; + } + if (avail >= runtime->control->avail_min) + wake_up(&runtime->sleep); + return 0; +} + +static inline int snd_pcm_update_hw_ptr_interrupt(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_uframes_t pos; + snd_pcm_uframes_t new_hw_ptr, hw_ptr_interrupt; + snd_pcm_sframes_t delta; + + pos = snd_pcm_update_hw_ptr_pos(substream, runtime); + if (pos == SNDRV_PCM_POS_XRUN) { + xrun(substream); + return -EPIPE; + } + if (runtime->period_size == runtime->buffer_size) + goto __next_buf; + new_hw_ptr = runtime->hw_ptr_base + pos; + hw_ptr_interrupt = runtime->hw_ptr_interrupt + runtime->period_size; + + delta = hw_ptr_interrupt - new_hw_ptr; + if (delta > 0) { + if ((snd_pcm_uframes_t)delta < runtime->buffer_size / 2) { +#ifdef CONFIG_SND_DEBUG + if (runtime->periods > 1 && substream->pstr->xrun_debug) { + snd_printd(KERN_ERR "Unexpected hw_pointer value [1] (stream = %i, delta: -%ld, max jitter = %ld): wrong interrupt acknowledge?\n", substream->stream, (long) delta, runtime->buffer_size / 2); + if (substream->pstr->xrun_debug > 1) + dump_stack(); + } +#endif + return 0; + } + __next_buf: + runtime->hw_ptr_base += runtime->buffer_size; + if (runtime->hw_ptr_base == runtime->boundary) + runtime->hw_ptr_base = 0; + new_hw_ptr = runtime->hw_ptr_base + pos; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, new_hw_ptr); + + runtime->status->hw_ptr = new_hw_ptr; + runtime->hw_ptr_interrupt = new_hw_ptr - new_hw_ptr % runtime->period_size; + + return snd_pcm_update_hw_ptr_post(substream, runtime); +} + +/* CAUTION: call it with irq disabled */ +int snd_pcm_update_hw_ptr(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_uframes_t pos; + snd_pcm_uframes_t old_hw_ptr, new_hw_ptr; + snd_pcm_sframes_t delta; + + old_hw_ptr = runtime->status->hw_ptr; + pos = snd_pcm_update_hw_ptr_pos(substream, runtime); + if (pos == SNDRV_PCM_POS_XRUN) { + xrun(substream); + return -EPIPE; + } + new_hw_ptr = runtime->hw_ptr_base + pos; + + delta = old_hw_ptr - new_hw_ptr; + if (delta > 0) { + if ((snd_pcm_uframes_t)delta < runtime->buffer_size / 2) { +#ifdef CONFIG_SND_DEBUG + if (runtime->periods > 2 && substream->pstr->xrun_debug) { + snd_printd(KERN_ERR "Unexpected hw_pointer value [2] (stream = %i, delta: -%ld, max jitter = %ld): wrong interrupt acknowledge?\n", substream->stream, (long) delta, runtime->buffer_size / 2); + if (substream->pstr->xrun_debug > 1) + dump_stack(); + } +#endif + return 0; + } + runtime->hw_ptr_base += runtime->buffer_size; + if (runtime->hw_ptr_base == runtime->boundary) + runtime->hw_ptr_base = 0; + new_hw_ptr = runtime->hw_ptr_base + pos; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, new_hw_ptr); + + runtime->status->hw_ptr = new_hw_ptr; + + return snd_pcm_update_hw_ptr_post(substream, runtime); +} + +/** + * snd_pcm_set_ops - set the PCM operators + * @pcm: the pcm instance + * @direction: stream direction, SNDRV_PCM_STREAM_XXX + * @ops: the operator table + * + * Sets the given PCM operators to the pcm instance. + */ +void snd_pcm_set_ops(snd_pcm_t *pcm, int direction, snd_pcm_ops_t *ops) +{ + snd_pcm_str_t *stream = &pcm->streams[direction]; + snd_pcm_substream_t *substream; + + for (substream = stream->substream; substream != NULL; substream = substream->next) + substream->ops = ops; +} + + +/** + * snd_pcm_sync - set the PCM sync id + * @substream: the pcm substream + * + * Sets the PCM sync identifier for the card. + */ +void snd_pcm_set_sync(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + + runtime->sync.id32[0] = substream->pcm->card->number; + runtime->sync.id32[1] = -1; + runtime->sync.id32[2] = -1; + runtime->sync.id32[3] = -1; +} + +/* + * Standard ioctl routine + */ + +/* Code taken from alsa-lib */ +#define assert(a) snd_assert((a), return -EINVAL) + +static inline unsigned int div32(unsigned int a, unsigned int b, + unsigned int *r) +{ + if (b == 0) { + *r = 0; + return UINT_MAX; + } + *r = a % b; + return a / b; +} + +static inline unsigned int div_down(unsigned int a, unsigned int b) +{ + if (b == 0) + return UINT_MAX; + return a / b; +} + +static inline unsigned int div_up(unsigned int a, unsigned int b) +{ + unsigned int r; + unsigned int q; + if (b == 0) + return UINT_MAX; + q = div32(a, b, &r); + if (r) + ++q; + return q; +} + +static inline unsigned int mul(unsigned int a, unsigned int b) +{ + if (a == 0) + return 0; + if (div_down(UINT_MAX, a) < b) + return UINT_MAX; + return a * b; +} + +static inline unsigned int muldiv32(unsigned int a, unsigned int b, + unsigned int c, unsigned int *r) +{ + u_int64_t n = (u_int64_t) a * b; + if (c == 0) { + snd_assert(n > 0, ); + *r = 0; + return UINT_MAX; + } + div64_32(&n, c, r); + if (n >= UINT_MAX) { + *r = 0; + return UINT_MAX; + } + return n; +} + +static int snd_interval_refine_min(snd_interval_t *i, unsigned int min, int openmin) +{ + int changed = 0; + assert(!snd_interval_empty(i)); + if (i->min < min) { + i->min = min; + i->openmin = openmin; + changed = 1; + } else if (i->min == min && !i->openmin && openmin) { + i->openmin = 1; + changed = 1; + } + if (i->integer) { + if (i->openmin) { + i->min++; + i->openmin = 0; + } + } + if (snd_interval_checkempty(i)) { + snd_interval_none(i); + return -EINVAL; + } + return changed; +} + +static int snd_interval_refine_max(snd_interval_t *i, unsigned int max, int openmax) +{ + int changed = 0; + assert(!snd_interval_empty(i)); + if (i->max > max) { + i->max = max; + i->openmax = openmax; + changed = 1; + } else if (i->max == max && !i->openmax && openmax) { + i->openmax = 1; + changed = 1; + } + if (i->integer) { + if (i->openmax) { + i->max--; + i->openmax = 0; + } + } + if (snd_interval_checkempty(i)) { + snd_interval_none(i); + return -EINVAL; + } + return changed; +} + +/** + * snd_interval_refine - refine the interval value of configurator + * @i: the interval value to refine + * @v: the interval value to refer to + * + * Refines the interval value with the reference value. + * The interval is changed to the range satisfying both intervals. + * The interval status (min, max, integer, etc.) are evaluated. + * + * Returns non-zero if the value is changed, zero if not changed. + */ +int snd_interval_refine(snd_interval_t *i, const snd_interval_t *v) +{ + int changed = 0; + assert(!snd_interval_empty(i)); + if (i->min < v->min) { + i->min = v->min; + i->openmin = v->openmin; + changed = 1; + } else if (i->min == v->min && !i->openmin && v->openmin) { + i->openmin = 1; + changed = 1; + } + if (i->max > v->max) { + i->max = v->max; + i->openmax = v->openmax; + changed = 1; + } else if (i->max == v->max && !i->openmax && v->openmax) { + i->openmax = 1; + changed = 1; + } + if (!i->integer && v->integer) { + i->integer = 1; + changed = 1; + } + if (i->integer) { + if (i->openmin) { + i->min++; + i->openmin = 0; + } + if (i->openmax) { + i->max--; + i->openmax = 0; + } + } else if (!i->openmin && !i->openmax && i->min == i->max) + i->integer = 1; + if (snd_interval_checkempty(i)) { + snd_interval_none(i); + return -EINVAL; + } + return changed; +} + +static int snd_interval_refine_first(snd_interval_t *i) +{ + assert(!snd_interval_empty(i)); + if (snd_interval_single(i)) + return 0; + i->max = i->min; + i->openmax = i->openmin; + if (i->openmax) + i->max++; + return 1; +} + +static int snd_interval_refine_last(snd_interval_t *i) +{ + assert(!snd_interval_empty(i)); + if (snd_interval_single(i)) + return 0; + i->min = i->max; + i->openmin = i->openmax; + if (i->openmin) + i->min--; + return 1; +} + +static int snd_interval_refine_set(snd_interval_t *i, unsigned int val) +{ + snd_interval_t t; + t.empty = 0; + t.min = t.max = val; + t.openmin = t.openmax = 0; + t.integer = 1; + return snd_interval_refine(i, &t); +} + +void snd_interval_mul(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c) +{ + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = mul(a->min, b->min); + c->openmin = (a->openmin || b->openmin); + c->max = mul(a->max, b->max); + c->openmax = (a->openmax || b->openmax); + c->integer = (a->integer && b->integer); +} + +/** + * snd_interval_div - refine the interval value with division + * + * c = a / b + * + * Returns non-zero if the value is changed, zero if not changed. + */ +void snd_interval_div(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c) +{ + unsigned int r; + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = div32(a->min, b->max, &r); + c->openmin = (r || a->openmin || b->openmax); + if (b->min > 0) { + c->max = div32(a->max, b->min, &r); + if (r) { + c->max++; + c->openmax = 1; + } else + c->openmax = (a->openmax || b->openmin); + } else { + c->max = UINT_MAX; + c->openmax = 0; + } + c->integer = 0; +} + +/** + * snd_interval_muldivk - refine the interval value + * + * c = a * b / k + * + * Returns non-zero if the value is changed, zero if not changed. + */ +void snd_interval_muldivk(const snd_interval_t *a, const snd_interval_t *b, + unsigned int k, snd_interval_t *c) +{ + unsigned int r; + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = muldiv32(a->min, b->min, k, &r); + c->openmin = (r || a->openmin || b->openmin); + c->max = muldiv32(a->max, b->max, k, &r); + if (r) { + c->max++; + c->openmax = 1; + } else + c->openmax = (a->openmax || b->openmax); + c->integer = 0; +} + +/** + * snd_interval_mulkdiv - refine the interval value + * + * c = a * k / b + * + * Returns non-zero if the value is changed, zero if not changed. + */ +void snd_interval_mulkdiv(const snd_interval_t *a, unsigned int k, + const snd_interval_t *b, snd_interval_t *c) +{ + unsigned int r; + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = muldiv32(a->min, k, b->max, &r); + c->openmin = (r || a->openmin || b->openmax); + if (b->min > 0) { + c->max = muldiv32(a->max, k, b->min, &r); + if (r) { + c->max++; + c->openmax = 1; + } else + c->openmax = (a->openmax || b->openmin); + } else { + c->max = UINT_MAX; + c->openmax = 0; + } + c->integer = 0; +} + +#undef assert +/* ---- */ + + +/** + * snd_interval_ratnum - refine the interval value + * + * Returns non-zero if the value is changed, zero if not changed. + */ +int snd_interval_ratnum(snd_interval_t *i, + unsigned int rats_count, ratnum_t *rats, + unsigned int *nump, unsigned int *denp) +{ + unsigned int best_num, best_diff, best_den; + unsigned int k; + snd_interval_t t; + int err; + + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num = rats[k].num; + unsigned int den; + unsigned int q = i->min; + int diff; + if (q == 0) + q = 1; + den = div_down(num, q); + if (den < rats[k].den_min) + continue; + if (den > rats[k].den_max) + den = rats[k].den_max; + else { + unsigned int r; + r = (den - rats[k].den_min) % rats[k].den_step; + if (r != 0) + den -= r; + } + diff = num - q * den; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.min = div_down(best_num, best_den); + t.openmin = !!(best_num % best_den); + + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num = rats[k].num; + unsigned int den; + unsigned int q = i->max; + int diff; + if (q == 0) { + i->empty = 1; + return -EINVAL; + } + den = div_up(num, q); + if (den > rats[k].den_max) + continue; + if (den < rats[k].den_min) + den = rats[k].den_min; + else { + unsigned int r; + r = (den - rats[k].den_min) % rats[k].den_step; + if (r != 0) + den += rats[k].den_step - r; + } + diff = q * den - num; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.max = div_up(best_num, best_den); + t.openmax = !!(best_num % best_den); + t.integer = 0; + err = snd_interval_refine(i, &t); + if (err < 0) + return err; + + if (snd_interval_single(i)) { + if (nump) + *nump = best_num; + if (denp) + *denp = best_den; + } + return err; +} + +/** + * snd_interval_ratden - refine the interval value + * + * Returns non-zero if the value is changed, zero if not changed. + */ +static int snd_interval_ratden(snd_interval_t *i, + unsigned int rats_count, ratden_t *rats, + unsigned int *nump, unsigned int *denp) +{ + unsigned int best_num, best_diff, best_den; + unsigned int k; + snd_interval_t t; + int err; + + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num; + unsigned int den = rats[k].den; + unsigned int q = i->min; + int diff; + num = mul(q, den); + if (num > rats[k].num_max) + continue; + if (num < rats[k].num_min) + num = rats[k].num_max; + else { + unsigned int r; + r = (num - rats[k].num_min) % rats[k].num_step; + if (r != 0) + num += rats[k].num_step - r; + } + diff = num - q * den; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.min = div_down(best_num, best_den); + t.openmin = !!(best_num % best_den); + + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num; + unsigned int den = rats[k].den; + unsigned int q = i->max; + int diff; + num = mul(q, den); + if (num < rats[k].num_min) + continue; + if (num > rats[k].num_max) + num = rats[k].num_max; + else { + unsigned int r; + r = (num - rats[k].num_min) % rats[k].num_step; + if (r != 0) + num -= r; + } + diff = q * den - num; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.max = div_up(best_num, best_den); + t.openmax = !!(best_num % best_den); + t.integer = 0; + err = snd_interval_refine(i, &t); + if (err < 0) + return err; + + if (snd_interval_single(i)) { + if (nump) + *nump = best_num; + if (denp) + *denp = best_den; + } + return err; +} + +/** + * snd_interval_list - refine the interval value from the list + * @i: the interval value to refine + * @count: the number of elements in the list + * @list: the value list + * @mask: the bit-mask to evaluate + * + * Refines the interval value from the list. + * When mask is non-zero, only the elements corresponding to bit 1 are + * evaluated. + * + * Returns non-zero if the value is changed, zero if not changed. + */ +int snd_interval_list(snd_interval_t *i, unsigned int count, unsigned int *list, unsigned int mask) +{ + unsigned int k; + int changed = 0; + for (k = 0; k < count; k++) { + if (mask && !(mask & (1 << k))) + continue; + if (i->min == list[k] && !i->openmin) + goto _l1; + if (i->min < list[k]) { + i->min = list[k]; + i->openmin = 0; + changed = 1; + goto _l1; + } + } + i->empty = 1; + return -EINVAL; + _l1: + for (k = count; k-- > 0;) { + if (mask && !(mask & (1 << k))) + continue; + if (i->max == list[k] && !i->openmax) + goto _l2; + if (i->max > list[k]) { + i->max = list[k]; + i->openmax = 0; + changed = 1; + goto _l2; + } + } + i->empty = 1; + return -EINVAL; + _l2: + if (snd_interval_checkempty(i)) { + i->empty = 1; + return -EINVAL; + } + return changed; +} + +static int snd_interval_step(snd_interval_t *i, unsigned int min, unsigned int step) +{ + unsigned int n; + int changed = 0; + n = (i->min - min) % step; + if (n != 0 || i->openmin) { + i->min += step - n; + changed = 1; + } + n = (i->max - min) % step; + if (n != 0 || i->openmax) { + i->max -= n; + changed = 1; + } + if (snd_interval_checkempty(i)) { + i->empty = 1; + return -EINVAL; + } + return changed; +} + +/* Info constraints helpers */ + +/** + * snd_pcm_hw_rule_add - add the hw-constraint rule + * @runtime: the pcm runtime instance + * @cond: condition bits + * @var: the variable to evaluate + * @func: the evaluation function + * @private: the private data pointer passed to function + * @dep: the dependent variables + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_rule_add(snd_pcm_runtime_t *runtime, unsigned int cond, + int var, + snd_pcm_hw_rule_func_t func, void *private, + int dep, ...) +{ + snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints; + snd_pcm_hw_rule_t *c; + unsigned int k; + va_list args; + va_start(args, dep); + if (constrs->rules_num >= constrs->rules_all) { + snd_pcm_hw_rule_t *new; + unsigned int new_rules = constrs->rules_all + 16; + new = kcalloc(new_rules, sizeof(*c), GFP_KERNEL); + if (!new) + return -ENOMEM; + if (constrs->rules) { + memcpy(new, constrs->rules, + constrs->rules_num * sizeof(*c)); + kfree(constrs->rules); + } + constrs->rules = new; + constrs->rules_all = new_rules; + } + c = &constrs->rules[constrs->rules_num]; + c->cond = cond; + c->func = func; + c->var = var; + c->private = private; + k = 0; + while (1) { + snd_assert(k < ARRAY_SIZE(c->deps), return -EINVAL); + c->deps[k++] = dep; + if (dep < 0) + break; + dep = va_arg(args, int); + } + constrs->rules_num++; + va_end(args); + return 0; +} + +/** + * snd_pcm_hw_constraint_mask + */ +int snd_pcm_hw_constraint_mask(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var, + u_int32_t mask) +{ + snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints; + snd_mask_t *maskp = constrs_mask(constrs, var); + *maskp->bits &= mask; + memset(maskp->bits + 1, 0, (SNDRV_MASK_MAX-32) / 8); /* clear rest */ + if (*maskp->bits == 0) + return -EINVAL; + return 0; +} + +/** + * snd_pcm_hw_constraint_mask64 + */ +int snd_pcm_hw_constraint_mask64(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var, + u_int64_t mask) +{ + snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints; + snd_mask_t *maskp = constrs_mask(constrs, var); + maskp->bits[0] &= (u_int32_t)mask; + maskp->bits[1] &= (u_int32_t)(mask >> 32); + memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX-64) / 8); /* clear rest */ + if (! maskp->bits[0] && ! maskp->bits[1]) + return -EINVAL; + return 0; +} + +/** + * snd_pcm_hw_constraint_integer + */ +int snd_pcm_hw_constraint_integer(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var) +{ + snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints; + return snd_interval_setinteger(constrs_interval(constrs, var)); +} + +/** + * snd_pcm_hw_constraint_minmax + */ +int snd_pcm_hw_constraint_minmax(snd_pcm_runtime_t *runtime, snd_pcm_hw_param_t var, + unsigned int min, unsigned int max) +{ + snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints; + snd_interval_t t; + t.min = min; + t.max = max; + t.openmin = t.openmax = 0; + t.integer = 0; + return snd_interval_refine(constrs_interval(constrs, var), &t); +} + +static int snd_pcm_hw_rule_list(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_pcm_hw_constraint_list_t *list = rule->private; + return snd_interval_list(hw_param_interval(params, rule->var), list->count, list->list, list->mask); +} + + +/** + * snd_pcm_hw_constraint_list + */ +int snd_pcm_hw_constraint_list(snd_pcm_runtime_t *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + snd_pcm_hw_constraint_list_t *l) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_list, l, + var, -1); +} + +static int snd_pcm_hw_rule_ratnums(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_pcm_hw_constraint_ratnums_t *r = rule->private; + unsigned int num = 0, den = 0; + int err; + err = snd_interval_ratnum(hw_param_interval(params, rule->var), + r->nrats, r->rats, &num, &den); + if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) { + params->rate_num = num; + params->rate_den = den; + } + return err; +} + +/** + * snd_pcm_hw_constraint_ratnums + */ +int snd_pcm_hw_constraint_ratnums(snd_pcm_runtime_t *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + snd_pcm_hw_constraint_ratnums_t *r) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_ratnums, r, + var, -1); +} + +static int snd_pcm_hw_rule_ratdens(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_pcm_hw_constraint_ratdens_t *r = rule->private; + unsigned int num = 0, den = 0; + int err = snd_interval_ratden(hw_param_interval(params, rule->var), + r->nrats, r->rats, &num, &den); + if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) { + params->rate_num = num; + params->rate_den = den; + } + return err; +} + +/** + * snd_pcm_hw_constraint_ratdens + */ +int snd_pcm_hw_constraint_ratdens(snd_pcm_runtime_t *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + snd_pcm_hw_constraint_ratdens_t *r) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_ratdens, r, + var, -1); +} + +static int snd_pcm_hw_rule_msbits(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + unsigned int l = (unsigned long) rule->private; + int width = l & 0xffff; + unsigned int msbits = l >> 16; + snd_interval_t *i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); + if (snd_interval_single(i) && snd_interval_value(i) == width) + params->msbits = msbits; + return 0; +} + +/** + * snd_pcm_hw_constraint_msbits + */ +int snd_pcm_hw_constraint_msbits(snd_pcm_runtime_t *runtime, + unsigned int cond, + unsigned int width, + unsigned int msbits) +{ + unsigned long l = (msbits << 16) | width; + return snd_pcm_hw_rule_add(runtime, cond, -1, + snd_pcm_hw_rule_msbits, + (void*) l, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); +} + +static int snd_pcm_hw_rule_step(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + unsigned long step = (unsigned long) rule->private; + return snd_interval_step(hw_param_interval(params, rule->var), 0, step); +} + +/** + * snd_pcm_hw_constraint_step + */ +int snd_pcm_hw_constraint_step(snd_pcm_runtime_t *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + unsigned long step) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_step, (void *) step, + var, -1); +} + +static int snd_pcm_hw_rule_pow2(snd_pcm_hw_params_t *params, snd_pcm_hw_rule_t *rule) +{ + static int pow2_sizes[] = { + 1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7, + 1<<8, 1<<9, 1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15, + 1<<16, 1<<17, 1<<18, 1<<19, 1<<20, 1<<21, 1<<22, 1<<23, + 1<<24, 1<<25, 1<<26, 1<<27, 1<<28, 1<<29, 1<<30 + }; + return snd_interval_list(hw_param_interval(params, rule->var), + ARRAY_SIZE(pow2_sizes), pow2_sizes, 0); +} + +/** + * snd_pcm_hw_constraint_pow2 + */ +int snd_pcm_hw_constraint_pow2(snd_pcm_runtime_t *runtime, + unsigned int cond, + snd_pcm_hw_param_t var) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_pow2, NULL, + var, -1); +} + +/* To use the same code we have in alsa-lib */ +#define snd_pcm_t snd_pcm_substream_t +#define assert(i) snd_assert((i), return -EINVAL) +#ifndef INT_MIN +#define INT_MIN ((int)((unsigned int)INT_MAX+1)) +#endif + +void _snd_pcm_hw_param_any(snd_pcm_hw_params_t *params, snd_pcm_hw_param_t var) +{ + if (hw_is_mask(var)) { + snd_mask_any(hw_param_mask(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + return; + } + if (hw_is_interval(var)) { + snd_interval_any(hw_param_interval(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + return; + } + snd_BUG(); +} + +/** + * snd_pcm_hw_param_any + */ +int snd_pcm_hw_param_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + _snd_pcm_hw_param_any(params, var); + return snd_pcm_hw_refine(pcm, params); +} + +void _snd_pcm_hw_params_any(snd_pcm_hw_params_t *params) +{ + unsigned int k; + memset(params, 0, sizeof(*params)); + for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) + _snd_pcm_hw_param_any(params, k); + for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) + _snd_pcm_hw_param_any(params, k); + params->info = ~0U; +} + +/** + * snd_pcm_hw_params_any + * + * Fill PARAMS with full configuration space boundaries + */ +int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) +{ + _snd_pcm_hw_params_any(params); + return snd_pcm_hw_refine(pcm, params); +} + +/** + * snd_pcm_hw_param_value + * + * Return the value for field PAR if it's fixed in configuration space + * defined by PARAMS. Return -EINVAL otherwise + */ +int snd_pcm_hw_param_value(const snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, int *dir) +{ + if (hw_is_mask(var)) { + const snd_mask_t *mask = hw_param_mask_c(params, var); + if (!snd_mask_single(mask)) + return -EINVAL; + if (dir) + *dir = 0; + return snd_mask_value(mask); + } + if (hw_is_interval(var)) { + const snd_interval_t *i = hw_param_interval_c(params, var); + if (!snd_interval_single(i)) + return -EINVAL; + if (dir) + *dir = i->openmin; + return snd_interval_value(i); + } + assert(0); + return -EINVAL; +} + +/** + * snd_pcm_hw_param_value_min + * + * Return the minimum value for field PAR. + */ +unsigned int snd_pcm_hw_param_value_min(const snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, int *dir) +{ + if (hw_is_mask(var)) { + if (dir) + *dir = 0; + return snd_mask_min(hw_param_mask_c(params, var)); + } + if (hw_is_interval(var)) { + const snd_interval_t *i = hw_param_interval_c(params, var); + if (dir) + *dir = i->openmin; + return snd_interval_min(i); + } + assert(0); + return -EINVAL; +} + +/** + * snd_pcm_hw_param_value_max + * + * Return the maximum value for field PAR. + */ +unsigned int snd_pcm_hw_param_value_max(const snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, int *dir) +{ + if (hw_is_mask(var)) { + if (dir) + *dir = 0; + return snd_mask_max(hw_param_mask_c(params, var)); + } + if (hw_is_interval(var)) { + const snd_interval_t *i = hw_param_interval_c(params, var); + if (dir) + *dir = - (int) i->openmax; + return snd_interval_max(i); + } + assert(0); + return -EINVAL; +} + +void _snd_pcm_hw_param_setempty(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + if (hw_is_mask(var)) { + snd_mask_none(hw_param_mask(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } else if (hw_is_interval(var)) { + snd_interval_none(hw_param_interval(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } else { + snd_BUG(); + } +} + +int _snd_pcm_hw_param_setinteger(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + int changed; + assert(hw_is_interval(var)); + changed = snd_interval_setinteger(hw_param_interval(params, var)); + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_setinteger + * + * Inside configuration space defined by PARAMS remove from PAR all + * non integer values. Reduce configuration space accordingly. + * Return -EINVAL if the configuration space is empty + */ +int snd_pcm_hw_param_setinteger(snd_pcm_t *pcm, + snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + int changed = _snd_pcm_hw_param_setinteger(params, var); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return 0; +} + +int _snd_pcm_hw_param_first(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + int changed; + if (hw_is_mask(var)) + changed = snd_mask_refine_first(hw_param_mask(params, var)); + else if (hw_is_interval(var)) + changed = snd_interval_refine_first(hw_param_interval(params, var)); + else { + assert(0); + return -EINVAL; + } + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + + +/** + * snd_pcm_hw_param_first + * + * Inside configuration space defined by PARAMS remove from PAR all + * values > minimum. Reduce configuration space accordingly. + * Return the minimum. + */ +int snd_pcm_hw_param_first(snd_pcm_t *pcm, + snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, int *dir) +{ + int changed = _snd_pcm_hw_param_first(params, var); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + assert(err >= 0); + } + return snd_pcm_hw_param_value(params, var, dir); +} + +int _snd_pcm_hw_param_last(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + int changed; + if (hw_is_mask(var)) + changed = snd_mask_refine_last(hw_param_mask(params, var)); + else if (hw_is_interval(var)) + changed = snd_interval_refine_last(hw_param_interval(params, var)); + else { + assert(0); + return -EINVAL; + } + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + + +/** + * snd_pcm_hw_param_last + * + * Inside configuration space defined by PARAMS remove from PAR all + * values < maximum. Reduce configuration space accordingly. + * Return the maximum. + */ +int snd_pcm_hw_param_last(snd_pcm_t *pcm, + snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, int *dir) +{ + int changed = _snd_pcm_hw_param_last(params, var); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + assert(err >= 0); + } + return snd_pcm_hw_param_value(params, var, dir); +} + +int _snd_pcm_hw_param_min(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, unsigned int val, int dir) +{ + int changed; + int open = 0; + if (dir) { + if (dir > 0) { + open = 1; + } else if (dir < 0) { + if (val > 0) { + open = 1; + val--; + } + } + } + if (hw_is_mask(var)) + changed = snd_mask_refine_min(hw_param_mask(params, var), val + !!open); + else if (hw_is_interval(var)) + changed = snd_interval_refine_min(hw_param_interval(params, var), val, open); + else { + assert(0); + return -EINVAL; + } + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_min + * + * Inside configuration space defined by PARAMS remove from PAR all + * values < VAL. Reduce configuration space accordingly. + * Return new minimum or -EINVAL if the configuration space is empty + */ +int snd_pcm_hw_param_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, unsigned int val, int *dir) +{ + int changed = _snd_pcm_hw_param_min(params, var, val, dir ? *dir : 0); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value_min(params, var, dir); +} + +int _snd_pcm_hw_param_max(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, unsigned int val, int dir) +{ + int changed; + int open = 0; + if (dir) { + if (dir < 0) { + open = 1; + } else if (dir > 0) { + open = 1; + val++; + } + } + if (hw_is_mask(var)) { + if (val == 0 && open) { + snd_mask_none(hw_param_mask(params, var)); + changed = -EINVAL; + } else + changed = snd_mask_refine_max(hw_param_mask(params, var), val - !!open); + } else if (hw_is_interval(var)) + changed = snd_interval_refine_max(hw_param_interval(params, var), val, open); + else { + assert(0); + return -EINVAL; + } + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_max + * + * Inside configuration space defined by PARAMS remove from PAR all + * values >= VAL + 1. Reduce configuration space accordingly. + * Return new maximum or -EINVAL if the configuration space is empty + */ +int snd_pcm_hw_param_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, unsigned int val, int *dir) +{ + int changed = _snd_pcm_hw_param_max(params, var, val, dir ? *dir : 0); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value_max(params, var, dir); +} + +int _snd_pcm_hw_param_set(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, unsigned int val, int dir) +{ + int changed; + if (hw_is_mask(var)) { + snd_mask_t *m = hw_param_mask(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_mask_none(m); + } else { + if (dir > 0) + val++; + else if (dir < 0) + val--; + changed = snd_mask_refine_set(hw_param_mask(params, var), val); + } + } else if (hw_is_interval(var)) { + snd_interval_t *i = hw_param_interval(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_interval_none(i); + } else if (dir == 0) + changed = snd_interval_refine_set(i, val); + else { + snd_interval_t t; + t.openmin = 1; + t.openmax = 1; + t.empty = 0; + t.integer = 0; + if (dir < 0) { + t.min = val - 1; + t.max = val; + } else { + t.min = val; + t.max = val+1; + } + changed = snd_interval_refine(i, &t); + } + } else { + assert(0); + return -EINVAL; + } + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_set + * + * Inside configuration space defined by PARAMS remove from PAR all + * values != VAL. Reduce configuration space accordingly. + * Return VAL or -EINVAL if the configuration space is empty + */ +int snd_pcm_hw_param_set(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, unsigned int val, int dir) +{ + int changed = _snd_pcm_hw_param_set(params, var, val, dir); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value(params, var, NULL); +} + +int _snd_pcm_hw_param_mask(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, const snd_mask_t *val) +{ + int changed; + assert(hw_is_mask(var)); + changed = snd_mask_refine(hw_param_mask(params, var), val); + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_mask + * + * Inside configuration space defined by PARAMS remove from PAR all values + * not contained in MASK. Reduce configuration space accordingly. + * This function can be called only for SNDRV_PCM_HW_PARAM_ACCESS, + * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT. + * Return 0 on success or -EINVAL + * if the configuration space is empty + */ +int snd_pcm_hw_param_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, const snd_mask_t *val) +{ + int changed = _snd_pcm_hw_param_mask(params, var, val); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return 0; +} + +static int boundary_sub(int a, int adir, + int b, int bdir, + int *c, int *cdir) +{ + adir = adir < 0 ? -1 : (adir > 0 ? 1 : 0); + bdir = bdir < 0 ? -1 : (bdir > 0 ? 1 : 0); + *c = a - b; + *cdir = adir - bdir; + if (*cdir == -2) { + assert(*c > INT_MIN); + (*c)--; + } else if (*cdir == 2) { + assert(*c < INT_MAX); + (*c)++; + } + return 0; +} + +static int boundary_lt(unsigned int a, int adir, + unsigned int b, int bdir) +{ + assert(a > 0 || adir >= 0); + assert(b > 0 || bdir >= 0); + if (adir < 0) { + a--; + adir = 1; + } else if (adir > 0) + adir = 1; + if (bdir < 0) { + b--; + bdir = 1; + } else if (bdir > 0) + bdir = 1; + return a < b || (a == b && adir < bdir); +} + +/* Return 1 if min is nearer to best than max */ +static int boundary_nearer(int min, int mindir, + int best, int bestdir, + int max, int maxdir) +{ + int dmin, dmindir; + int dmax, dmaxdir; + boundary_sub(best, bestdir, min, mindir, &dmin, &dmindir); + boundary_sub(max, maxdir, best, bestdir, &dmax, &dmaxdir); + return boundary_lt(dmin, dmindir, dmax, dmaxdir); +} + +/** + * snd_pcm_hw_param_near + * + * Inside configuration space defined by PARAMS set PAR to the available value + * nearest to VAL. Reduce configuration space accordingly. + * This function cannot be called for SNDRV_PCM_HW_PARAM_ACCESS, + * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT. + * Return the value found. + */ +int snd_pcm_hw_param_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, unsigned int best, int *dir) +{ + snd_pcm_hw_params_t *save = NULL; + int v; + unsigned int saved_min; + int last = 0; + int min, max; + int mindir, maxdir; + int valdir = dir ? *dir : 0; + /* FIXME */ + if (best > INT_MAX) + best = INT_MAX; + min = max = best; + mindir = maxdir = valdir; + if (maxdir > 0) + maxdir = 0; + else if (maxdir == 0) + maxdir = -1; + else { + maxdir = 1; + max--; + } + save = kmalloc(sizeof(*save), GFP_KERNEL); + if (save == NULL) + return -ENOMEM; + *save = *params; + saved_min = min; + min = snd_pcm_hw_param_min(pcm, params, var, min, &mindir); + if (min >= 0) { + snd_pcm_hw_params_t *params1; + if (max < 0) + goto _end; + if ((unsigned int)min == saved_min && mindir == valdir) + goto _end; + params1 = kmalloc(sizeof(*params1), GFP_KERNEL); + if (params1 == NULL) { + kfree(save); + return -ENOMEM; + } + *params1 = *save; + max = snd_pcm_hw_param_max(pcm, params1, var, max, &maxdir); + if (max < 0) { + kfree(params1); + goto _end; + } + if (boundary_nearer(max, maxdir, best, valdir, min, mindir)) { + *params = *params1; + last = 1; + } + kfree(params1); + } else { + *params = *save; + max = snd_pcm_hw_param_max(pcm, params, var, max, &maxdir); + assert(max >= 0); + last = 1; + } + _end: + kfree(save); + if (last) + v = snd_pcm_hw_param_last(pcm, params, var, dir); + else + v = snd_pcm_hw_param_first(pcm, params, var, dir); + assert(v >= 0); + return v; +} + +/** + * snd_pcm_hw_param_choose + * + * Choose one configuration from configuration space defined by PARAMS + * The configuration chosen is that obtained fixing in this order: + * first access, first format, first subformat, min channels, + * min rate, min period time, max buffer size, min tick time + */ +int snd_pcm_hw_params_choose(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) +{ + int err; + + err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_ACCESS, NULL); + assert(err >= 0); + + err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_FORMAT, NULL); + assert(err >= 0); + + err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_SUBFORMAT, NULL); + assert(err >= 0); + + err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_CHANNELS, NULL); + assert(err >= 0); + + err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_RATE, NULL); + assert(err >= 0); + + err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_PERIOD_TIME, NULL); + assert(err >= 0); + + err = snd_pcm_hw_param_last(pcm, params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, NULL); + assert(err >= 0); + + err = snd_pcm_hw_param_first(pcm, params, SNDRV_PCM_HW_PARAM_TICK_TIME, NULL); + assert(err >= 0); + + return 0; +} + +#undef snd_pcm_t +#undef assert + +static int snd_pcm_lib_ioctl_reset(snd_pcm_substream_t *substream, + void *arg) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned long flags; + snd_pcm_stream_lock_irqsave(substream, flags); + if (snd_pcm_running(substream) && + snd_pcm_update_hw_ptr(substream) >= 0) + runtime->status->hw_ptr %= runtime->buffer_size; + else + runtime->status->hw_ptr = 0; + snd_pcm_stream_unlock_irqrestore(substream, flags); + return 0; +} + +static int snd_pcm_lib_ioctl_channel_info(snd_pcm_substream_t *substream, + void *arg) +{ + snd_pcm_channel_info_t *info = arg; + snd_pcm_runtime_t *runtime = substream->runtime; + int width; + if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) { + info->offset = -1; + return 0; + } + width = snd_pcm_format_physical_width(runtime->format); + if (width < 0) + return width; + info->offset = 0; + switch (runtime->access) { + case SNDRV_PCM_ACCESS_MMAP_INTERLEAVED: + case SNDRV_PCM_ACCESS_RW_INTERLEAVED: + info->first = info->channel * width; + info->step = runtime->channels * width; + break; + case SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED: + case SNDRV_PCM_ACCESS_RW_NONINTERLEAVED: + { + size_t size = runtime->dma_bytes / runtime->channels; + info->first = info->channel * size * 8; + info->step = width; + break; + } + default: + snd_BUG(); + break; + } + return 0; +} + +/** + * snd_pcm_lib_ioctl - a generic PCM ioctl callback + * @substream: the pcm substream instance + * @cmd: ioctl command + * @arg: ioctl argument + * + * Processes the generic ioctl commands for PCM. + * Can be passed as the ioctl callback for PCM ops. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_ioctl(snd_pcm_substream_t *substream, + unsigned int cmd, void *arg) +{ + switch (cmd) { + case SNDRV_PCM_IOCTL1_INFO: + return 0; + case SNDRV_PCM_IOCTL1_RESET: + return snd_pcm_lib_ioctl_reset(substream, arg); + case SNDRV_PCM_IOCTL1_CHANNEL_INFO: + return snd_pcm_lib_ioctl_channel_info(substream, arg); + } + return -ENXIO; +} + +/* + * Conditions + */ + +static void snd_pcm_system_tick_set(snd_pcm_substream_t *substream, + unsigned long ticks) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (ticks == 0) + del_timer(&runtime->tick_timer); + else { + ticks += (1000000 / HZ) - 1; + ticks /= (1000000 / HZ); + mod_timer(&runtime->tick_timer, jiffies + ticks); + } +} + +/* Temporary alias */ +void snd_pcm_tick_set(snd_pcm_substream_t *substream, unsigned long ticks) +{ + snd_pcm_system_tick_set(substream, ticks); +} + +void snd_pcm_tick_prepare(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_uframes_t frames = ULONG_MAX; + snd_pcm_uframes_t avail, dist; + unsigned int ticks; + u_int64_t n; + u_int32_t r; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (runtime->silence_size >= runtime->boundary) { + frames = 1; + } else if (runtime->silence_size > 0 && + runtime->silence_filled < runtime->buffer_size) { + snd_pcm_sframes_t noise_dist; + noise_dist = snd_pcm_playback_hw_avail(runtime) + runtime->silence_filled; + snd_assert(noise_dist <= (snd_pcm_sframes_t)runtime->silence_threshold, ); + frames = noise_dist - runtime->silence_threshold; + } + avail = snd_pcm_playback_avail(runtime); + } else { + avail = snd_pcm_capture_avail(runtime); + } + if (avail < runtime->control->avail_min) { + snd_pcm_sframes_t n = runtime->control->avail_min - avail; + if (n > 0 && frames > (snd_pcm_uframes_t)n) + frames = n; + } + if (avail < runtime->buffer_size) { + snd_pcm_sframes_t n = runtime->buffer_size - avail; + if (n > 0 && frames > (snd_pcm_uframes_t)n) + frames = n; + } + if (frames == ULONG_MAX) { + snd_pcm_tick_set(substream, 0); + return; + } + dist = runtime->status->hw_ptr - runtime->hw_ptr_base; + /* Distance to next interrupt */ + dist = runtime->period_size - dist % runtime->period_size; + if (dist <= frames) { + snd_pcm_tick_set(substream, 0); + return; + } + /* the base time is us */ + n = frames; + n *= 1000000; + div64_32(&n, runtime->tick_time * runtime->rate, &r); + ticks = n + (r > 0 ? 1 : 0); + if (ticks < runtime->sleep_min) + ticks = runtime->sleep_min; + snd_pcm_tick_set(substream, (unsigned long) ticks); +} + +void snd_pcm_tick_elapsed(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime; + unsigned long flags; + + snd_assert(substream != NULL, return); + runtime = substream->runtime; + snd_assert(runtime != NULL, return); + + snd_pcm_stream_lock_irqsave(substream, flags); + if (!snd_pcm_running(substream) || + snd_pcm_update_hw_ptr(substream) < 0) + goto _end; + if (runtime->sleep_min) + snd_pcm_tick_prepare(substream); + _end: + snd_pcm_stream_unlock_irqrestore(substream, flags); +} + +/** + * snd_pcm_period_elapsed - update the pcm status for the next period + * @substream: the pcm substream instance + * + * This function is called from the interrupt handler when the + * PCM has processed the period size. It will update the current + * pointer, set up the tick, wake up sleepers, etc. + * + * Even if more than one periods have elapsed since the last call, you + * have to call this only once. + */ +void snd_pcm_period_elapsed(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime; + unsigned long flags; + + snd_assert(substream != NULL, return); + runtime = substream->runtime; + snd_assert(runtime != NULL, return); + + if (runtime->transfer_ack_begin) + runtime->transfer_ack_begin(substream); + + snd_pcm_stream_lock_irqsave(substream, flags); + if (!snd_pcm_running(substream) || + snd_pcm_update_hw_ptr_interrupt(substream) < 0) + goto _end; + + if (substream->timer_running) + snd_timer_interrupt(substream->timer, 1); + if (runtime->sleep_min) + snd_pcm_tick_prepare(substream); + _end: + snd_pcm_stream_unlock_irqrestore(substream, flags); + if (runtime->transfer_ack_end) + runtime->transfer_ack_end(substream); + kill_fasync(&runtime->fasync, SIGIO, POLL_IN); +} + +static int snd_pcm_lib_write_transfer(snd_pcm_substream_t *substream, + unsigned int hwoff, + unsigned long data, unsigned int off, + snd_pcm_uframes_t frames) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + char __user *buf = (char __user *) data + frames_to_bytes(runtime, off); + if (substream->ops->copy) { + if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0) + return err; + } else { + char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff); + snd_assert(runtime->dma_area, return -EFAULT); + if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames))) + return -EFAULT; + } + return 0; +} + +typedef int (*transfer_f)(snd_pcm_substream_t *substream, unsigned int hwoff, + unsigned long data, unsigned int off, + snd_pcm_uframes_t size); + +static snd_pcm_sframes_t snd_pcm_lib_write1(snd_pcm_substream_t *substream, + unsigned long data, + snd_pcm_uframes_t size, + int nonblock, + transfer_f transfer) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_uframes_t xfer = 0; + snd_pcm_uframes_t offset = 0; + int err = 0; + + if (size == 0) + return 0; + if (size > runtime->xfer_align) + size -= size % runtime->xfer_align; + + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PAUSED: + break; + case SNDRV_PCM_STATE_XRUN: + err = -EPIPE; + goto _end_unlock; + case SNDRV_PCM_STATE_SUSPENDED: + err = -ESTRPIPE; + goto _end_unlock; + default: + err = -EBADFD; + goto _end_unlock; + } + + while (size > 0) { + snd_pcm_uframes_t frames, appl_ptr, appl_ofs; + snd_pcm_uframes_t avail; + snd_pcm_uframes_t cont; + if (runtime->sleep_min == 0 && runtime->status->state == SNDRV_PCM_STATE_RUNNING) + snd_pcm_update_hw_ptr(substream); + avail = snd_pcm_playback_avail(runtime); + if (((avail < runtime->control->avail_min && size > avail) || + (size >= runtime->xfer_align && avail < runtime->xfer_align))) { + wait_queue_t wait; + enum { READY, SIGNALED, ERROR, SUSPENDED, EXPIRED } state; + long tout; + + if (nonblock) { + err = -EAGAIN; + goto _end_unlock; + } + + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + while (1) { + if (signal_pending(current)) { + state = SIGNALED; + break; + } + set_current_state(TASK_INTERRUPTIBLE); + snd_pcm_stream_unlock_irq(substream); + tout = schedule_timeout(10 * HZ); + snd_pcm_stream_lock_irq(substream); + if (tout == 0) { + if (runtime->status->state != SNDRV_PCM_STATE_PREPARED && + runtime->status->state != SNDRV_PCM_STATE_PAUSED) { + state = runtime->status->state == SNDRV_PCM_STATE_SUSPENDED ? SUSPENDED : EXPIRED; + break; + } + } + switch (runtime->status->state) { + case SNDRV_PCM_STATE_XRUN: + case SNDRV_PCM_STATE_DRAINING: + state = ERROR; + goto _end_loop; + case SNDRV_PCM_STATE_SUSPENDED: + state = SUSPENDED; + goto _end_loop; + default: + break; + } + avail = snd_pcm_playback_avail(runtime); + if (avail >= runtime->control->avail_min) { + state = READY; + break; + } + } + _end_loop: + remove_wait_queue(&runtime->sleep, &wait); + + switch (state) { + case ERROR: + err = -EPIPE; + goto _end_unlock; + case SUSPENDED: + err = -ESTRPIPE; + goto _end_unlock; + case SIGNALED: + err = -ERESTARTSYS; + goto _end_unlock; + case EXPIRED: + snd_printd("playback write error (DMA or IRQ trouble?)\n"); + err = -EIO; + goto _end_unlock; + default: + break; + } + } + if (avail > runtime->xfer_align) + avail -= avail % runtime->xfer_align; + frames = size > avail ? avail : size; + cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size; + if (frames > cont) + frames = cont; + snd_assert(frames != 0, snd_pcm_stream_unlock_irq(substream); return -EINVAL); + appl_ptr = runtime->control->appl_ptr; + appl_ofs = appl_ptr % runtime->buffer_size; + snd_pcm_stream_unlock_irq(substream); + if ((err = transfer(substream, appl_ofs, data, offset, frames)) < 0) + goto _end; + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_XRUN: + err = -EPIPE; + goto _end_unlock; + case SNDRV_PCM_STATE_SUSPENDED: + err = -ESTRPIPE; + goto _end_unlock; + default: + break; + } + appl_ptr += frames; + if (appl_ptr >= runtime->boundary) + appl_ptr -= runtime->boundary; + runtime->control->appl_ptr = appl_ptr; + if (substream->ops->ack) + substream->ops->ack(substream); + + offset += frames; + size -= frames; + xfer += frames; + if (runtime->status->state == SNDRV_PCM_STATE_PREPARED && + snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) { + err = snd_pcm_start(substream); + if (err < 0) + goto _end_unlock; + } + if (runtime->sleep_min && + runtime->status->state == SNDRV_PCM_STATE_RUNNING) + snd_pcm_tick_prepare(substream); + } + _end_unlock: + snd_pcm_stream_unlock_irq(substream); + _end: + return xfer > 0 ? (snd_pcm_sframes_t)xfer : err; +} + +snd_pcm_sframes_t snd_pcm_lib_write(snd_pcm_substream_t *substream, const void __user *buf, snd_pcm_uframes_t size) +{ + snd_pcm_runtime_t *runtime; + int nonblock; + + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + snd_assert(runtime != NULL, return -ENXIO); + snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + snd_assert(substream->ffile != NULL, return -ENXIO); + nonblock = !!(substream->ffile->f_flags & O_NONBLOCK); +#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) + if (substream->oss.oss) { + snd_pcm_oss_setup_t *setup = substream->oss.setup; + if (setup != NULL) { + if (setup->nonblock) + nonblock = 1; + else if (setup->block) + nonblock = 0; + } + } +#endif + + if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED && + runtime->channels > 1) + return -EINVAL; + return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock, + snd_pcm_lib_write_transfer); +} + +static int snd_pcm_lib_writev_transfer(snd_pcm_substream_t *substream, + unsigned int hwoff, + unsigned long data, unsigned int off, + snd_pcm_uframes_t frames) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + void __user **bufs = (void __user **)data; + int channels = runtime->channels; + int c; + if (substream->ops->copy) { + snd_assert(substream->ops->silence != NULL, return -EINVAL); + for (c = 0; c < channels; ++c, ++bufs) { + if (*bufs == NULL) { + if ((err = substream->ops->silence(substream, c, hwoff, frames)) < 0) + return err; + } else { + char __user *buf = *bufs + samples_to_bytes(runtime, off); + if ((err = substream->ops->copy(substream, c, hwoff, buf, frames)) < 0) + return err; + } + } + } else { + /* default transfer behaviour */ + size_t dma_csize = runtime->dma_bytes / channels; + snd_assert(runtime->dma_area, return -EFAULT); + for (c = 0; c < channels; ++c, ++bufs) { + char *hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, hwoff); + if (*bufs == NULL) { + snd_pcm_format_set_silence(runtime->format, hwbuf, frames); + } else { + char __user *buf = *bufs + samples_to_bytes(runtime, off); + if (copy_from_user(hwbuf, buf, samples_to_bytes(runtime, frames))) + return -EFAULT; + } + } + } + return 0; +} + +snd_pcm_sframes_t snd_pcm_lib_writev(snd_pcm_substream_t *substream, + void __user **bufs, + snd_pcm_uframes_t frames) +{ + snd_pcm_runtime_t *runtime; + int nonblock; + + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + snd_assert(runtime != NULL, return -ENXIO); + snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + snd_assert(substream->ffile != NULL, return -ENXIO); + nonblock = !!(substream->ffile->f_flags & O_NONBLOCK); +#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) + if (substream->oss.oss) { + snd_pcm_oss_setup_t *setup = substream->oss.setup; + if (setup != NULL) { + if (setup->nonblock) + nonblock = 1; + else if (setup->block) + nonblock = 0; + } + } +#endif + + if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) + return -EINVAL; + return snd_pcm_lib_write1(substream, (unsigned long)bufs, frames, + nonblock, snd_pcm_lib_writev_transfer); +} + +static int snd_pcm_lib_read_transfer(snd_pcm_substream_t *substream, + unsigned int hwoff, + unsigned long data, unsigned int off, + snd_pcm_uframes_t frames) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + char __user *buf = (char __user *) data + frames_to_bytes(runtime, off); + if (substream->ops->copy) { + if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0) + return err; + } else { + char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff); + snd_assert(runtime->dma_area, return -EFAULT); + if (copy_to_user(buf, hwbuf, frames_to_bytes(runtime, frames))) + return -EFAULT; + } + return 0; +} + +static snd_pcm_sframes_t snd_pcm_lib_read1(snd_pcm_substream_t *substream, + unsigned long data, + snd_pcm_uframes_t size, + int nonblock, + transfer_f transfer) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_uframes_t xfer = 0; + snd_pcm_uframes_t offset = 0; + int err = 0; + + if (size == 0) + return 0; + if (size > runtime->xfer_align) + size -= size % runtime->xfer_align; + + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_PREPARED: + if (size >= runtime->start_threshold) { + err = snd_pcm_start(substream); + if (err < 0) + goto _end_unlock; + } + break; + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PAUSED: + break; + case SNDRV_PCM_STATE_XRUN: + err = -EPIPE; + goto _end_unlock; + case SNDRV_PCM_STATE_SUSPENDED: + err = -ESTRPIPE; + goto _end_unlock; + default: + err = -EBADFD; + goto _end_unlock; + } + + while (size > 0) { + snd_pcm_uframes_t frames, appl_ptr, appl_ofs; + snd_pcm_uframes_t avail; + snd_pcm_uframes_t cont; + if (runtime->sleep_min == 0 && runtime->status->state == SNDRV_PCM_STATE_RUNNING) + snd_pcm_update_hw_ptr(substream); + __draining: + avail = snd_pcm_capture_avail(runtime); + if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) { + if (avail < runtime->xfer_align) { + err = -EPIPE; + goto _end_unlock; + } + } else if ((avail < runtime->control->avail_min && size > avail) || + (size >= runtime->xfer_align && avail < runtime->xfer_align)) { + wait_queue_t wait; + enum { READY, SIGNALED, ERROR, SUSPENDED, EXPIRED } state; + long tout; + + if (nonblock) { + err = -EAGAIN; + goto _end_unlock; + } + + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + while (1) { + if (signal_pending(current)) { + state = SIGNALED; + break; + } + set_current_state(TASK_INTERRUPTIBLE); + snd_pcm_stream_unlock_irq(substream); + tout = schedule_timeout(10 * HZ); + snd_pcm_stream_lock_irq(substream); + if (tout == 0) { + if (runtime->status->state != SNDRV_PCM_STATE_PREPARED && + runtime->status->state != SNDRV_PCM_STATE_PAUSED) { + state = runtime->status->state == SNDRV_PCM_STATE_SUSPENDED ? SUSPENDED : EXPIRED; + break; + } + } + switch (runtime->status->state) { + case SNDRV_PCM_STATE_XRUN: + state = ERROR; + goto _end_loop; + case SNDRV_PCM_STATE_SUSPENDED: + state = SUSPENDED; + goto _end_loop; + case SNDRV_PCM_STATE_DRAINING: + goto __draining; + default: + break; + } + avail = snd_pcm_capture_avail(runtime); + if (avail >= runtime->control->avail_min) { + state = READY; + break; + } + } + _end_loop: + remove_wait_queue(&runtime->sleep, &wait); + + switch (state) { + case ERROR: + err = -EPIPE; + goto _end_unlock; + case SUSPENDED: + err = -ESTRPIPE; + goto _end_unlock; + case SIGNALED: + err = -ERESTARTSYS; + goto _end_unlock; + case EXPIRED: + snd_printd("capture read error (DMA or IRQ trouble?)\n"); + err = -EIO; + goto _end_unlock; + default: + break; + } + } + if (avail > runtime->xfer_align) + avail -= avail % runtime->xfer_align; + frames = size > avail ? avail : size; + cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size; + if (frames > cont) + frames = cont; + snd_assert(frames != 0, snd_pcm_stream_unlock_irq(substream); return -EINVAL); + appl_ptr = runtime->control->appl_ptr; + appl_ofs = appl_ptr % runtime->buffer_size; + snd_pcm_stream_unlock_irq(substream); + if ((err = transfer(substream, appl_ofs, data, offset, frames)) < 0) + goto _end; + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_XRUN: + err = -EPIPE; + goto _end_unlock; + case SNDRV_PCM_STATE_SUSPENDED: + err = -ESTRPIPE; + goto _end_unlock; + default: + break; + } + appl_ptr += frames; + if (appl_ptr >= runtime->boundary) + appl_ptr -= runtime->boundary; + runtime->control->appl_ptr = appl_ptr; + if (substream->ops->ack) + substream->ops->ack(substream); + + offset += frames; + size -= frames; + xfer += frames; + if (runtime->sleep_min && + runtime->status->state == SNDRV_PCM_STATE_RUNNING) + snd_pcm_tick_prepare(substream); + } + _end_unlock: + snd_pcm_stream_unlock_irq(substream); + _end: + return xfer > 0 ? (snd_pcm_sframes_t)xfer : err; +} + +snd_pcm_sframes_t snd_pcm_lib_read(snd_pcm_substream_t *substream, void __user *buf, snd_pcm_uframes_t size) +{ + snd_pcm_runtime_t *runtime; + int nonblock; + + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + snd_assert(runtime != NULL, return -ENXIO); + snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + snd_assert(substream->ffile != NULL, return -ENXIO); + nonblock = !!(substream->ffile->f_flags & O_NONBLOCK); +#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) + if (substream->oss.oss) { + snd_pcm_oss_setup_t *setup = substream->oss.setup; + if (setup != NULL) { + if (setup->nonblock) + nonblock = 1; + else if (setup->block) + nonblock = 0; + } + } +#endif + if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED) + return -EINVAL; + return snd_pcm_lib_read1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_read_transfer); +} + +static int snd_pcm_lib_readv_transfer(snd_pcm_substream_t *substream, + unsigned int hwoff, + unsigned long data, unsigned int off, + snd_pcm_uframes_t frames) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + void __user **bufs = (void __user **)data; + int channels = runtime->channels; + int c; + if (substream->ops->copy) { + for (c = 0; c < channels; ++c, ++bufs) { + char __user *buf; + if (*bufs == NULL) + continue; + buf = *bufs + samples_to_bytes(runtime, off); + if ((err = substream->ops->copy(substream, c, hwoff, buf, frames)) < 0) + return err; + } + } else { + snd_pcm_uframes_t dma_csize = runtime->dma_bytes / channels; + snd_assert(runtime->dma_area, return -EFAULT); + for (c = 0; c < channels; ++c, ++bufs) { + char *hwbuf; + char __user *buf; + if (*bufs == NULL) + continue; + + hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, hwoff); + buf = *bufs + samples_to_bytes(runtime, off); + if (copy_to_user(buf, hwbuf, samples_to_bytes(runtime, frames))) + return -EFAULT; + } + } + return 0; +} + +snd_pcm_sframes_t snd_pcm_lib_readv(snd_pcm_substream_t *substream, + void __user **bufs, + snd_pcm_uframes_t frames) +{ + snd_pcm_runtime_t *runtime; + int nonblock; + + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + snd_assert(runtime != NULL, return -ENXIO); + snd_assert(substream->ops->copy != NULL || runtime->dma_area != NULL, return -EINVAL); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + snd_assert(substream->ffile != NULL, return -ENXIO); + nonblock = !!(substream->ffile->f_flags & O_NONBLOCK); +#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) + if (substream->oss.oss) { + snd_pcm_oss_setup_t *setup = substream->oss.setup; + if (setup != NULL) { + if (setup->nonblock) + nonblock = 1; + else if (setup->block) + nonblock = 0; + } + } +#endif + + if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) + return -EINVAL; + return snd_pcm_lib_read1(substream, (unsigned long)bufs, frames, nonblock, snd_pcm_lib_readv_transfer); +} + +/* + * Exported symbols + */ + +EXPORT_SYMBOL(snd_interval_refine); +EXPORT_SYMBOL(snd_interval_list); +EXPORT_SYMBOL(snd_interval_ratnum); +EXPORT_SYMBOL(snd_interval_muldivk); +EXPORT_SYMBOL(snd_interval_mulkdiv); +EXPORT_SYMBOL(snd_interval_div); +EXPORT_SYMBOL(_snd_pcm_hw_params_any); +EXPORT_SYMBOL(_snd_pcm_hw_param_min); +EXPORT_SYMBOL(_snd_pcm_hw_param_set); +EXPORT_SYMBOL(_snd_pcm_hw_param_setempty); +EXPORT_SYMBOL(_snd_pcm_hw_param_setinteger); +EXPORT_SYMBOL(snd_pcm_hw_param_value_min); +EXPORT_SYMBOL(snd_pcm_hw_param_value_max); +EXPORT_SYMBOL(snd_pcm_hw_param_mask); +EXPORT_SYMBOL(snd_pcm_hw_param_first); +EXPORT_SYMBOL(snd_pcm_hw_param_last); +EXPORT_SYMBOL(snd_pcm_hw_param_near); +EXPORT_SYMBOL(snd_pcm_hw_param_set); +EXPORT_SYMBOL(snd_pcm_hw_refine); +EXPORT_SYMBOL(snd_pcm_hw_params); +EXPORT_SYMBOL(snd_pcm_hw_constraints_init); +EXPORT_SYMBOL(snd_pcm_hw_constraints_complete); +EXPORT_SYMBOL(snd_pcm_hw_constraint_list); +EXPORT_SYMBOL(snd_pcm_hw_constraint_step); +EXPORT_SYMBOL(snd_pcm_hw_constraint_ratnums); +EXPORT_SYMBOL(snd_pcm_hw_constraint_ratdens); +EXPORT_SYMBOL(snd_pcm_hw_constraint_msbits); +EXPORT_SYMBOL(snd_pcm_hw_constraint_minmax); +EXPORT_SYMBOL(snd_pcm_hw_constraint_integer); +EXPORT_SYMBOL(snd_pcm_hw_constraint_pow2); +EXPORT_SYMBOL(snd_pcm_hw_rule_add); +EXPORT_SYMBOL(snd_pcm_set_ops); +EXPORT_SYMBOL(snd_pcm_set_sync); +EXPORT_SYMBOL(snd_pcm_lib_ioctl); +EXPORT_SYMBOL(snd_pcm_stop); +EXPORT_SYMBOL(snd_pcm_period_elapsed); +EXPORT_SYMBOL(snd_pcm_lib_write); +EXPORT_SYMBOL(snd_pcm_lib_read); +EXPORT_SYMBOL(snd_pcm_lib_writev); +EXPORT_SYMBOL(snd_pcm_lib_readv); +EXPORT_SYMBOL(snd_pcm_lib_buffer_bytes); +EXPORT_SYMBOL(snd_pcm_lib_period_bytes); +/* pcm_memory.c */ +EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all); +EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages); +EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all); +EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page); +EXPORT_SYMBOL(snd_pcm_lib_malloc_pages); +EXPORT_SYMBOL(snd_pcm_lib_free_pages); diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c new file mode 100644 index 000000000000..f1d5f7a6ee0c --- /dev/null +++ b/sound/core/pcm_memory.c @@ -0,0 +1,363 @@ +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <linux/time.h> +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/info.h> +#include <sound/initval.h> + +static int preallocate_dma = 1; +module_param(preallocate_dma, int, 0444); +MODULE_PARM_DESC(preallocate_dma, "Preallocate DMA memory when the PCM devices are initialized."); + +static int maximum_substreams = 4; +module_param(maximum_substreams, int, 0444); +MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA memory."); + +static const size_t snd_minimum_buffer = 16384; + + +/* + * try to allocate as the large pages as possible. + * stores the resultant memory size in *res_size. + * + * the minimum size is snd_minimum_buffer. it should be power of 2. + */ +static int preallocate_pcm_pages(snd_pcm_substream_t *substream, size_t size) +{ + struct snd_dma_buffer *dmab = &substream->dma_buffer; + int err; + + snd_assert(size > 0, return -EINVAL); + + /* already reserved? */ + if (snd_dma_get_reserved_buf(dmab, substream->dma_buf_id) > 0) { + if (dmab->bytes >= size) + return 0; /* yes */ + /* no, free the reserved block */ + snd_dma_free_pages(dmab); + dmab->bytes = 0; + } + + do { + if ((err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev, + size, dmab)) < 0) { + if (err != -ENOMEM) + return err; /* fatal error */ + } else + return 0; + size >>= 1; + } while (size >= snd_minimum_buffer); + dmab->bytes = 0; /* tell error */ + return 0; +} + +/* + * release the preallocated buffer if not yet done. + */ +static void snd_pcm_lib_preallocate_dma_free(snd_pcm_substream_t *substream) +{ + if (substream->dma_buffer.area == NULL) + return; + if (substream->dma_buf_id) + snd_dma_reserve_buf(&substream->dma_buffer, substream->dma_buf_id); + else + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; +} + +/** + * snd_pcm_lib_preallocate_free - release the preallocated buffer of the specified substream. + * @substream: the pcm substream instance + * + * Releases the pre-allocated buffer of the given substream. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_preallocate_free(snd_pcm_substream_t *substream) +{ + snd_pcm_lib_preallocate_dma_free(substream); + if (substream->proc_prealloc_entry) { + snd_info_unregister(substream->proc_prealloc_entry); + substream->proc_prealloc_entry = NULL; + } + return 0; +} + +/** + * snd_pcm_lib_preallocate_free_for_all - release all pre-allocated buffers on the pcm + * @pcm: the pcm instance + * + * Releases all the pre-allocated buffers on the given pcm. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_preallocate_free_for_all(snd_pcm_t *pcm) +{ + snd_pcm_substream_t *substream; + int stream; + + for (stream = 0; stream < 2; stream++) + for (substream = pcm->streams[stream].substream; substream; substream = substream->next) + snd_pcm_lib_preallocate_free(substream); + return 0; +} + +/* + * read callback for prealloc proc file + * + * prints the current allocated size in kB. + */ +static void snd_pcm_lib_preallocate_proc_read(snd_info_entry_t *entry, + snd_info_buffer_t *buffer) +{ + snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data; + snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_buffer.bytes / 1024); +} + +/* + * write callback for prealloc proc file + * + * accepts the preallocation size in kB. + */ +static void snd_pcm_lib_preallocate_proc_write(snd_info_entry_t *entry, + snd_info_buffer_t *buffer) +{ + snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data; + char line[64], str[64]; + size_t size; + struct snd_dma_buffer new_dmab; + + if (substream->runtime) { + buffer->error = -EBUSY; + return; + } + if (!snd_info_get_line(buffer, line, sizeof(line))) { + snd_info_get_str(str, line, sizeof(str)); + size = simple_strtoul(str, NULL, 10) * 1024; + if ((size != 0 && size < 8192) || size > substream->dma_max) { + buffer->error = -EINVAL; + return; + } + if (substream->dma_buffer.bytes == size) + return; + memset(&new_dmab, 0, sizeof(new_dmab)); + new_dmab.dev = substream->dma_buffer.dev; + if (size > 0) { + if (snd_dma_alloc_pages(substream->dma_buffer.dev.type, + substream->dma_buffer.dev.dev, + size, &new_dmab) < 0) { + buffer->error = -ENOMEM; + return; + } + substream->buffer_bytes_max = size; + } else { + substream->buffer_bytes_max = UINT_MAX; + } + if (substream->dma_buffer.area) + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer = new_dmab; + } else { + buffer->error = -EINVAL; + } +} + +/* + * pre-allocate the buffer and create a proc file for the substream + */ +static int snd_pcm_lib_preallocate_pages1(snd_pcm_substream_t *substream, + size_t size, size_t max) +{ + snd_info_entry_t *entry; + + if (size > 0 && preallocate_dma && substream->number < maximum_substreams) + preallocate_pcm_pages(substream, size); + + if (substream->dma_buffer.bytes > 0) + substream->buffer_bytes_max = substream->dma_buffer.bytes; + substream->dma_max = max; + if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) { + entry->c.text.read_size = 64; + entry->c.text.read = snd_pcm_lib_preallocate_proc_read; + entry->c.text.write_size = 64; + entry->c.text.write = snd_pcm_lib_preallocate_proc_write; + entry->private_data = substream; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + substream->proc_prealloc_entry = entry; + return 0; +} + + +/** + * snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type + * @substream: the pcm substream instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependant data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation for the given DMA buffer type. + * + * When substream->dma_buf_id is set, the function tries to look for + * the reserved buffer, and the buffer is not freed but reserved at + * destruction time. The dma_buf_id must be unique for all systems + * (in the same DMA buffer type) e.g. using snd_dma_pci_buf_id(). + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_preallocate_pages(snd_pcm_substream_t *substream, + int type, struct device *data, + size_t size, size_t max) +{ + substream->dma_buffer.dev.type = type; + substream->dma_buffer.dev.dev = data; + return snd_pcm_lib_preallocate_pages1(substream, size, max); +} + +/** + * snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continous memory type (all substreams) + * @substream: the pcm substream instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependant data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation to all substreams of the given pcm for the + * specified DMA type. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_preallocate_pages_for_all(snd_pcm_t *pcm, + int type, void *data, + size_t size, size_t max) +{ + snd_pcm_substream_t *substream; + int stream, err; + + for (stream = 0; stream < 2; stream++) + for (substream = pcm->streams[stream].substream; substream; substream = substream->next) + if ((err = snd_pcm_lib_preallocate_pages(substream, type, data, size, max)) < 0) + return err; + return 0; +} + +/** + * snd_pcm_sgbuf_ops_page - get the page struct at the given offset + * @substream: the pcm substream instance + * @offset: the buffer offset + * + * Returns the page struct at the given buffer offset. + * Used as the page callback of PCM ops. + */ +struct page *snd_pcm_sgbuf_ops_page(snd_pcm_substream_t *substream, unsigned long offset) +{ + struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream); + + unsigned int idx = offset >> PAGE_SHIFT; + if (idx >= (unsigned int)sgbuf->pages) + return NULL; + return sgbuf->page_table[idx]; +} + +/** + * snd_pcm_lib_malloc_pages - allocate the DMA buffer + * @substream: the substream to allocate the DMA buffer to + * @size: the requested buffer size in bytes + * + * Allocates the DMA buffer on the BUS type given earlier to + * snd_pcm_lib_preallocate_xxx_pages(). + * + * Returns 1 if the buffer is changed, 0 if not changed, or a negative + * code on failure. + */ +int snd_pcm_lib_malloc_pages(snd_pcm_substream_t *substream, size_t size) +{ + snd_pcm_runtime_t *runtime; + struct snd_dma_buffer *dmab = NULL; + + snd_assert(substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_UNKNOWN, return -EINVAL); + snd_assert(substream != NULL, return -EINVAL); + runtime = substream->runtime; + snd_assert(runtime != NULL, return -EINVAL); + + if (runtime->dma_buffer_p) { + /* perphaps, we might free the large DMA memory region + to save some space here, but the actual solution + costs us less time */ + if (runtime->dma_buffer_p->bytes >= size) { + runtime->dma_bytes = size; + return 0; /* ok, do not change */ + } + snd_pcm_lib_free_pages(substream); + } + if (substream->dma_buffer.area != NULL && substream->dma_buffer.bytes >= size) { + dmab = &substream->dma_buffer; /* use the pre-allocated buffer */ + } else { + dmab = kcalloc(1, sizeof(*dmab), GFP_KERNEL); + if (! dmab) + return -ENOMEM; + dmab->dev = substream->dma_buffer.dev; + if (snd_dma_alloc_pages(substream->dma_buffer.dev.type, + substream->dma_buffer.dev.dev, + size, dmab) < 0) { + kfree(dmab); + return -ENOMEM; + } + } + snd_pcm_set_runtime_buffer(substream, dmab); + runtime->dma_bytes = size; + return 1; /* area was changed */ +} + +/** + * snd_pcm_lib_free_pages - release the allocated DMA buffer. + * @substream: the substream to release the DMA buffer + * + * Releases the DMA buffer allocated via snd_pcm_lib_malloc_pages(). + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_free_pages(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime; + + snd_assert(substream != NULL, return -EINVAL); + runtime = substream->runtime; + snd_assert(runtime != NULL, return -EINVAL); + if (runtime->dma_area == NULL) + return 0; + if (runtime->dma_buffer_p != &substream->dma_buffer) { + /* it's a newly allocated buffer. release it now. */ + snd_dma_free_pages(runtime->dma_buffer_p); + kfree(runtime->dma_buffer_p); + } + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c new file mode 100644 index 000000000000..422b8db14154 --- /dev/null +++ b/sound/core/pcm_misc.c @@ -0,0 +1,481 @@ +/* + * PCM Interface - misc routines + * Copyright (c) 1998 by Jaroslav Kysela <perex@suse.cz> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#define SND_PCM_FORMAT_UNKNOWN (-1) + +/* NOTE: "signed" prefix must be given below since the default char is + * unsigned on some architectures! + */ +struct pcm_format_data { + unsigned char width; /* bit width */ + unsigned char phys; /* physical bit width */ + signed char le; /* 0 = big-endian, 1 = little-endian, -1 = others */ + signed char signd; /* 0 = unsigned, 1 = signed, -1 = others */ + unsigned char silence[8]; /* silence data to fill */ +}; + +static struct pcm_format_data pcm_formats[SNDRV_PCM_FORMAT_LAST+1] = { + [SNDRV_PCM_FORMAT_S8] = { + .width = 8, .phys = 8, .le = -1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U8] = { + .width = 8, .phys = 8, .le = -1, .signd = 0, + .silence = { 0x80 }, + }, + [SNDRV_PCM_FORMAT_S16_LE] = { + .width = 16, .phys = 16, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S16_BE] = { + .width = 16, .phys = 16, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U16_LE] = { + .width = 16, .phys = 16, .le = 1, .signd = 0, + .silence = { 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U16_BE] = { + .width = 16, .phys = 16, .le = 0, .signd = 0, + .silence = { 0x80, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S24_LE] = { + .width = 24, .phys = 32, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S24_BE] = { + .width = 24, .phys = 32, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U24_LE] = { + .width = 24, .phys = 32, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U24_BE] = { + .width = 24, .phys = 32, .le = 0, .signd = 0, + .silence = { 0x80, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S32_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S32_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U32_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U32_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = 0, + .silence = { 0x80, 0x00, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_FLOAT_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_FLOAT_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_FLOAT64_LE] = { + .width = 64, .phys = 64, .le = 1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_FLOAT64_BE] = { + .width = 64, .phys = 64, .le = 0, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_MU_LAW] = { + .width = 8, .phys = 8, .le = -1, .signd = -1, + .silence = { 0x7f }, + }, + [SNDRV_PCM_FORMAT_A_LAW] = { + .width = 8, .phys = 8, .le = -1, .signd = -1, + .silence = { 0x55 }, + }, + [SNDRV_PCM_FORMAT_IMA_ADPCM] = { + .width = 4, .phys = 4, .le = -1, .signd = -1, + .silence = {}, + }, + /* FIXME: the following three formats are not defined properly yet */ + [SNDRV_PCM_FORMAT_MPEG] = { + .le = -1, .signd = -1, + }, + [SNDRV_PCM_FORMAT_GSM] = { + .le = -1, .signd = -1, + }, + [SNDRV_PCM_FORMAT_SPECIAL] = { + .le = -1, .signd = -1, + }, + [SNDRV_PCM_FORMAT_S24_3LE] = { + .width = 24, .phys = 24, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S24_3BE] = { + .width = 24, .phys = 24, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U24_3LE] = { + .width = 24, .phys = 24, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U24_3BE] = { + .width = 24, .phys = 24, .le = 0, .signd = 0, + .silence = { 0x80, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S20_3LE] = { + .width = 20, .phys = 24, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S20_3BE] = { + .width = 20, .phys = 24, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U20_3LE] = { + .width = 20, .phys = 24, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x08 }, + }, + [SNDRV_PCM_FORMAT_U20_3BE] = { + .width = 20, .phys = 24, .le = 0, .signd = 0, + .silence = { 0x08, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S18_3LE] = { + .width = 18, .phys = 24, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S18_3BE] = { + .width = 18, .phys = 24, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U18_3LE] = { + .width = 18, .phys = 24, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x02 }, + }, + [SNDRV_PCM_FORMAT_U18_3BE] = { + .width = 18, .phys = 24, .le = 0, .signd = 0, + .silence = { 0x02, 0x00, 0x00 }, + }, +}; + + +/** + * snd_pcm_format_signed - Check the PCM format is signed linear + * @format: the format to check + * + * Returns 1 if the given PCM format is signed linear, 0 if unsigned + * linear, and a negative error code for non-linear formats. + */ +int snd_pcm_format_signed(snd_pcm_format_t format) +{ + int val; + if (format < 0 || format > SNDRV_PCM_FORMAT_LAST) + return -EINVAL; + if ((val = pcm_formats[format].signd) < 0) + return -EINVAL; + return val; +} + +/** + * snd_pcm_format_unsigned - Check the PCM format is unsigned linear + * @format: the format to check + * + * Returns 1 if the given PCM format is unsigned linear, 0 if signed + * linear, and a negative error code for non-linear formats. + */ +int snd_pcm_format_unsigned(snd_pcm_format_t format) +{ + int val; + + val = snd_pcm_format_signed(format); + if (val < 0) + return val; + return !val; +} + +/** + * snd_pcm_format_linear - Check the PCM format is linear + * @format: the format to check + * + * Returns 1 if the given PCM format is linear, 0 if not. + */ +int snd_pcm_format_linear(snd_pcm_format_t format) +{ + return snd_pcm_format_signed(format) >= 0; +} + +/** + * snd_pcm_format_little_endian - Check the PCM format is little-endian + * @format: the format to check + * + * Returns 1 if the given PCM format is little-endian, 0 if + * big-endian, or a negative error code if endian not specified. + */ +int snd_pcm_format_little_endian(snd_pcm_format_t format) +{ + int val; + if (format < 0 || format > SNDRV_PCM_FORMAT_LAST) + return -EINVAL; + if ((val = pcm_formats[format].le) < 0) + return -EINVAL; + return val; +} + +/** + * snd_pcm_format_big_endian - Check the PCM format is big-endian + * @format: the format to check + * + * Returns 1 if the given PCM format is big-endian, 0 if + * little-endian, or a negative error code if endian not specified. + */ +int snd_pcm_format_big_endian(snd_pcm_format_t format) +{ + int val; + + val = snd_pcm_format_little_endian(format); + if (val < 0) + return val; + return !val; +} + +/** + * snd_pcm_format_cpu_endian - Check the PCM format is CPU-endian + * @format: the format to check + * + * Returns 1 if the given PCM format is CPU-endian, 0 if + * opposite, or a negative error code if endian not specified. + */ +int snd_pcm_format_cpu_endian(snd_pcm_format_t format) +{ +#ifdef SNDRV_LITTLE_ENDIAN + return snd_pcm_format_little_endian(format); +#else + return snd_pcm_format_big_endian(format); +#endif +} + +/** + * snd_pcm_format_width - return the bit-width of the format + * @format: the format to check + * + * Returns the bit-width of the format, or a negative error code + * if unknown format. + */ +int snd_pcm_format_width(snd_pcm_format_t format) +{ + int val; + if (format < 0 || format > SNDRV_PCM_FORMAT_LAST) + return -EINVAL; + if ((val = pcm_formats[format].width) == 0) + return -EINVAL; + return val; +} + +/** + * snd_pcm_format_physical_width - return the physical bit-width of the format + * @format: the format to check + * + * Returns the physical bit-width of the format, or a negative error code + * if unknown format. + */ +int snd_pcm_format_physical_width(snd_pcm_format_t format) +{ + int val; + if (format < 0 || format > SNDRV_PCM_FORMAT_LAST) + return -EINVAL; + if ((val = pcm_formats[format].phys) == 0) + return -EINVAL; + return val; +} + +/** + * snd_pcm_format_size - return the byte size of samples on the given format + * @format: the format to check + * + * Returns the byte size of the given samples for the format, or a + * negative error code if unknown format. + */ +ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples) +{ + int phys_width = snd_pcm_format_physical_width(format); + if (phys_width < 0) + return -EINVAL; + return samples * phys_width / 8; +} + +/** + * snd_pcm_format_silence_64 - return the silent data in 8 bytes array + * @format: the format to check + * + * Returns the format pattern to fill or NULL if error. + */ +const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format) +{ + if (format < 0 || format > SNDRV_PCM_FORMAT_LAST) + return NULL; + if (! pcm_formats[format].phys) + return NULL; + return pcm_formats[format].silence; +} + +/** + * snd_pcm_format_set_silence - set the silence data on the buffer + * @format: the PCM format + * @data: the buffer pointer + * @samples: the number of samples to set silence + * + * Sets the silence data on the buffer for the given samples. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples) +{ + int width; + unsigned char *dst, *pat; + + if (format < 0 || format > SNDRV_PCM_FORMAT_LAST) + return -EINVAL; + if (samples == 0) + return 0; + width = pcm_formats[format].phys; /* physical width */ + pat = pcm_formats[format].silence; + if (! width) + return -EINVAL; + /* signed or 1 byte data */ + if (pcm_formats[format].signd == 1 || width <= 8) { + unsigned int bytes = samples * width / 8; + memset(data, *pat, bytes); + return 0; + } + /* non-zero samples, fill using a loop */ + width /= 8; + dst = data; +#if 0 + while (samples--) { + memcpy(dst, pat, width); + dst += width; + } +#else + /* a bit optimization for constant width */ + switch (width) { + case 2: + while (samples--) { + memcpy(dst, pat, 2); + dst += 2; + } + break; + case 3: + while (samples--) { + memcpy(dst, pat, 3); + dst += 3; + } + break; + case 4: + while (samples--) { + memcpy(dst, pat, 4); + dst += 4; + } + break; + case 8: + while (samples--) { + memcpy(dst, pat, 8); + dst += 8; + } + break; + } +#endif + return 0; +} + +/* [width][unsigned][bigendian] */ +static int linear_formats[4][2][2] = { + {{ SNDRV_PCM_FORMAT_S8, SNDRV_PCM_FORMAT_S8}, + { SNDRV_PCM_FORMAT_U8, SNDRV_PCM_FORMAT_U8}}, + {{SNDRV_PCM_FORMAT_S16_LE, SNDRV_PCM_FORMAT_S16_BE}, + {SNDRV_PCM_FORMAT_U16_LE, SNDRV_PCM_FORMAT_U16_BE}}, + {{SNDRV_PCM_FORMAT_S24_LE, SNDRV_PCM_FORMAT_S24_BE}, + {SNDRV_PCM_FORMAT_U24_LE, SNDRV_PCM_FORMAT_U24_BE}}, + {{SNDRV_PCM_FORMAT_S32_LE, SNDRV_PCM_FORMAT_S32_BE}, + {SNDRV_PCM_FORMAT_U32_LE, SNDRV_PCM_FORMAT_U32_BE}} +}; + +/** + * snd_pcm_build_linear_format - return the suitable linear format for the given condition + * @width: the bit-width + * @unsignd: 1 if unsigned, 0 if signed. + * @big_endian: 1 if big-endian, 0 if little-endian + * + * Returns the suitable linear format for the given condition. + */ +snd_pcm_format_t snd_pcm_build_linear_format(int width, int unsignd, int big_endian) +{ + if (width & 7) + return SND_PCM_FORMAT_UNKNOWN; + width = (width / 8) - 1; + if (width < 0 || width >= 4) + return SND_PCM_FORMAT_UNKNOWN; + return linear_formats[width][!!unsignd][!!big_endian]; +} + +/** + * snd_pcm_limit_hw_rates - determine rate_min/rate_max fields + * @runtime: the runtime instance + * + * Determines the rate_min and rate_max fields from the rates bits of + * the given runtime->hw. + * + * Returns zero if successful. + */ +int snd_pcm_limit_hw_rates(snd_pcm_runtime_t *runtime) +{ + static unsigned rates[] = { + /* ATTENTION: these values depend on the definition in pcm.h! */ + 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, + 64000, 88200, 96000, 176400, 192000 + }; + int i; + for (i = 0; i < (int)ARRAY_SIZE(rates); i++) { + if (runtime->hw.rates & (1 << i)) { + runtime->hw.rate_min = rates[i]; + break; + } + } + for (i = (int)ARRAY_SIZE(rates) - 1; i >= 0; i--) { + if (runtime->hw.rates & (1 << i)) { + runtime->hw.rate_max = rates[i]; + break; + } + } + return 0; +} diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c new file mode 100644 index 000000000000..cad9bbde9986 --- /dev/null +++ b/sound/core/pcm_native.c @@ -0,0 +1,3364 @@ +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/mm.h> +#include <linux/smp_lock.h> +#include <linux/file.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/uio.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/timer.h> +#include <sound/minors.h> +#include <asm/io.h> + +/* + * Compatibility + */ + +struct sndrv_pcm_hw_params_old { + unsigned int flags; + unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT - + SNDRV_PCM_HW_PARAM_ACCESS + 1]; + struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_TICK_TIME - + SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1]; + unsigned int rmask; + unsigned int cmask; + unsigned int info; + unsigned int msbits; + unsigned int rate_num; + unsigned int rate_den; + sndrv_pcm_uframes_t fifo_size; + unsigned char reserved[64]; +}; + +#define SNDRV_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct sndrv_pcm_hw_params_old) +#define SNDRV_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct sndrv_pcm_hw_params_old) + +static int snd_pcm_hw_refine_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams); +static int snd_pcm_hw_params_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams); + +/* + * + */ + +DEFINE_RWLOCK(snd_pcm_link_rwlock); +static DECLARE_RWSEM(snd_pcm_link_rwsem); + + +static inline mm_segment_t snd_enter_user(void) +{ + mm_segment_t fs = get_fs(); + set_fs(get_ds()); + return fs; +} + +static inline void snd_leave_user(mm_segment_t fs) +{ + set_fs(fs); +} + + + +int snd_pcm_info(snd_pcm_substream_t * substream, snd_pcm_info_t *info) +{ + snd_pcm_runtime_t * runtime; + snd_pcm_t *pcm = substream->pcm; + snd_pcm_str_t *pstr = substream->pstr; + + snd_assert(substream != NULL, return -ENXIO); + memset(info, 0, sizeof(*info)); + info->card = pcm->card->number; + info->device = pcm->device; + info->stream = substream->stream; + info->subdevice = substream->number; + strlcpy(info->id, pcm->id, sizeof(info->id)); + strlcpy(info->name, pcm->name, sizeof(info->name)); + info->dev_class = pcm->dev_class; + info->dev_subclass = pcm->dev_subclass; + info->subdevices_count = pstr->substream_count; + info->subdevices_avail = pstr->substream_count - pstr->substream_opened; + strlcpy(info->subname, substream->name, sizeof(info->subname)); + runtime = substream->runtime; + /* AB: FIXME!!! This is definitely nonsense */ + if (runtime) { + info->sync = runtime->sync; + substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_INFO, info); + } + return 0; +} + +int snd_pcm_info_user(snd_pcm_substream_t * substream, snd_pcm_info_t __user * _info) +{ + snd_pcm_info_t *info; + int err; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + err = snd_pcm_info(substream, info); + if (err >= 0) { + if (copy_to_user(_info, info, sizeof(*info))) + err = -EFAULT; + } + kfree(info); + return err; +} + +#undef RULES_DEBUG + +#ifdef RULES_DEBUG +#define HW_PARAM(v) [SNDRV_PCM_HW_PARAM_##v] = #v +char *snd_pcm_hw_param_names[] = { + HW_PARAM(ACCESS), + HW_PARAM(FORMAT), + HW_PARAM(SUBFORMAT), + HW_PARAM(SAMPLE_BITS), + HW_PARAM(FRAME_BITS), + HW_PARAM(CHANNELS), + HW_PARAM(RATE), + HW_PARAM(PERIOD_TIME), + HW_PARAM(PERIOD_SIZE), + HW_PARAM(PERIOD_BYTES), + HW_PARAM(PERIODS), + HW_PARAM(BUFFER_TIME), + HW_PARAM(BUFFER_SIZE), + HW_PARAM(BUFFER_BYTES), + HW_PARAM(TICK_TIME), +}; +#endif + +int snd_pcm_hw_refine(snd_pcm_substream_t *substream, + snd_pcm_hw_params_t *params) +{ + unsigned int k; + snd_pcm_hardware_t *hw; + snd_interval_t *i = NULL; + snd_mask_t *m = NULL; + snd_pcm_hw_constraints_t *constrs = &substream->runtime->hw_constraints; + unsigned int rstamps[constrs->rules_num]; + unsigned int vstamps[SNDRV_PCM_HW_PARAM_LAST_INTERVAL + 1]; + unsigned int stamp = 2; + int changed, again; + + params->info = 0; + params->fifo_size = 0; + if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_SAMPLE_BITS)) + params->msbits = 0; + if (params->rmask & (1 << SNDRV_PCM_HW_PARAM_RATE)) { + params->rate_num = 0; + params->rate_den = 0; + } + + for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) { + m = hw_param_mask(params, k); + if (snd_mask_empty(m)) + return -EINVAL; + if (!(params->rmask & (1 << k))) + continue; +#ifdef RULES_DEBUG + printk("%s = ", snd_pcm_hw_param_names[k]); + printk("%04x%04x%04x%04x -> ", m->bits[3], m->bits[2], m->bits[1], m->bits[0]); +#endif + changed = snd_mask_refine(m, constrs_mask(constrs, k)); +#ifdef RULES_DEBUG + printk("%04x%04x%04x%04x\n", m->bits[3], m->bits[2], m->bits[1], m->bits[0]); +#endif + if (changed) + params->cmask |= 1 << k; + if (changed < 0) + return changed; + } + + for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) { + i = hw_param_interval(params, k); + if (snd_interval_empty(i)) + return -EINVAL; + if (!(params->rmask & (1 << k))) + continue; +#ifdef RULES_DEBUG + printk("%s = ", snd_pcm_hw_param_names[k]); + if (i->empty) + printk("empty"); + else + printk("%c%u %u%c", + i->openmin ? '(' : '[', i->min, + i->max, i->openmax ? ')' : ']'); + printk(" -> "); +#endif + changed = snd_interval_refine(i, constrs_interval(constrs, k)); +#ifdef RULES_DEBUG + if (i->empty) + printk("empty\n"); + else + printk("%c%u %u%c\n", + i->openmin ? '(' : '[', i->min, + i->max, i->openmax ? ')' : ']'); +#endif + if (changed) + params->cmask |= 1 << k; + if (changed < 0) + return changed; + } + + for (k = 0; k < constrs->rules_num; k++) + rstamps[k] = 0; + for (k = 0; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) + vstamps[k] = (params->rmask & (1 << k)) ? 1 : 0; + do { + again = 0; + for (k = 0; k < constrs->rules_num; k++) { + snd_pcm_hw_rule_t *r = &constrs->rules[k]; + unsigned int d; + int doit = 0; + if (r->cond && !(r->cond & params->flags)) + continue; + for (d = 0; r->deps[d] >= 0; d++) { + if (vstamps[r->deps[d]] > rstamps[k]) { + doit = 1; + break; + } + } + if (!doit) + continue; +#ifdef RULES_DEBUG + printk("Rule %d [%p]: ", k, r->func); + if (r->var >= 0) { + printk("%s = ", snd_pcm_hw_param_names[r->var]); + if (hw_is_mask(r->var)) { + m = hw_param_mask(params, r->var); + printk("%x", *m->bits); + } else { + i = hw_param_interval(params, r->var); + if (i->empty) + printk("empty"); + else + printk("%c%u %u%c", + i->openmin ? '(' : '[', i->min, + i->max, i->openmax ? ')' : ']'); + } + } +#endif + changed = r->func(params, r); +#ifdef RULES_DEBUG + if (r->var >= 0) { + printk(" -> "); + if (hw_is_mask(r->var)) + printk("%x", *m->bits); + else { + if (i->empty) + printk("empty"); + else + printk("%c%u %u%c", + i->openmin ? '(' : '[', i->min, + i->max, i->openmax ? ')' : ']'); + } + } + printk("\n"); +#endif + rstamps[k] = stamp; + if (changed && r->var >= 0) { + params->cmask |= (1 << r->var); + vstamps[r->var] = stamp; + again = 1; + } + if (changed < 0) + return changed; + stamp++; + } + } while (again); + if (!params->msbits) { + i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); + if (snd_interval_single(i)) + params->msbits = snd_interval_value(i); + } + + if (!params->rate_den) { + i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (snd_interval_single(i)) { + params->rate_num = snd_interval_value(i); + params->rate_den = 1; + } + } + + hw = &substream->runtime->hw; + if (!params->info) + params->info = hw->info; + if (!params->fifo_size) + params->fifo_size = hw->fifo_size; + params->rmask = 0; + return 0; +} + +static int snd_pcm_hw_refine_user(snd_pcm_substream_t * substream, snd_pcm_hw_params_t __user * _params) +{ + snd_pcm_hw_params_t *params; + int err; + + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) { + err = -ENOMEM; + goto out; + } + if (copy_from_user(params, _params, sizeof(*params))) { + err = -EFAULT; + goto out; + } + err = snd_pcm_hw_refine(substream, params); + if (copy_to_user(_params, params, sizeof(*params))) { + if (!err) + err = -EFAULT; + } +out: + kfree(params); + return err; +} + +int snd_pcm_hw_params(snd_pcm_substream_t *substream, + snd_pcm_hw_params_t *params) +{ + snd_pcm_runtime_t *runtime; + int err; + unsigned int bits; + snd_pcm_uframes_t frames; + + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + snd_assert(runtime != NULL, return -ENXIO); + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + break; + default: + snd_pcm_stream_unlock_irq(substream); + return -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); +#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE) + if (!substream->oss.oss) +#endif + if (atomic_read(&runtime->mmap_count)) + return -EBADFD; + + params->rmask = ~0U; + err = snd_pcm_hw_refine(substream, params); + if (err < 0) + goto _error; + + err = snd_pcm_hw_params_choose(substream, params); + if (err < 0) + goto _error; + + if (substream->ops->hw_params != NULL) { + err = substream->ops->hw_params(substream, params); + if (err < 0) + goto _error; + } + + runtime->access = params_access(params); + runtime->format = params_format(params); + runtime->subformat = params_subformat(params); + runtime->channels = params_channels(params); + runtime->rate = params_rate(params); + runtime->period_size = params_period_size(params); + runtime->periods = params_periods(params); + runtime->buffer_size = params_buffer_size(params); + runtime->tick_time = params_tick_time(params); + runtime->info = params->info; + runtime->rate_num = params->rate_num; + runtime->rate_den = params->rate_den; + + bits = snd_pcm_format_physical_width(runtime->format); + runtime->sample_bits = bits; + bits *= runtime->channels; + runtime->frame_bits = bits; + frames = 1; + while (bits % 8 != 0) { + bits *= 2; + frames *= 2; + } + runtime->byte_align = bits / 8; + runtime->min_align = frames; + + /* Default sw params */ + runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE; + runtime->period_step = 1; + runtime->sleep_min = 0; + runtime->control->avail_min = runtime->period_size; + runtime->xfer_align = runtime->period_size; + runtime->start_threshold = 1; + runtime->stop_threshold = runtime->buffer_size; + runtime->silence_threshold = 0; + runtime->silence_size = 0; + runtime->boundary = runtime->buffer_size; + while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size) + runtime->boundary *= 2; + + snd_pcm_timer_resolution_change(substream); + runtime->status->state = SNDRV_PCM_STATE_SETUP; + return 0; + _error: + /* hardware might be unuseable from this time, + so we force application to retry to set + the correct hardware parameter settings */ + runtime->status->state = SNDRV_PCM_STATE_OPEN; + if (substream->ops->hw_free != NULL) + substream->ops->hw_free(substream); + return err; +} + +static int snd_pcm_hw_params_user(snd_pcm_substream_t * substream, snd_pcm_hw_params_t __user * _params) +{ + snd_pcm_hw_params_t *params; + int err; + + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) { + err = -ENOMEM; + goto out; + } + if (copy_from_user(params, _params, sizeof(*params))) { + err = -EFAULT; + goto out; + } + err = snd_pcm_hw_params(substream, params); + if (copy_to_user(_params, params, sizeof(*params))) { + if (!err) + err = -EFAULT; + } +out: + kfree(params); + return err; +} + +static int snd_pcm_hw_free(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime; + int result = 0; + + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + snd_assert(runtime != NULL, return -ENXIO); + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + break; + default: + snd_pcm_stream_unlock_irq(substream); + return -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); + if (atomic_read(&runtime->mmap_count)) + return -EBADFD; + if (substream->ops->hw_free) + result = substream->ops->hw_free(substream); + runtime->status->state = SNDRV_PCM_STATE_OPEN; + return result; +} + +static int snd_pcm_sw_params(snd_pcm_substream_t * substream, snd_pcm_sw_params_t *params) +{ + snd_pcm_runtime_t *runtime; + + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + snd_assert(runtime != NULL, return -ENXIO); + snd_pcm_stream_lock_irq(substream); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) { + snd_pcm_stream_unlock_irq(substream); + return -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); + + if (params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST) + return -EINVAL; + if (params->avail_min == 0) + return -EINVAL; + if (params->xfer_align == 0 || + params->xfer_align % runtime->min_align != 0) + return -EINVAL; + if (params->silence_size >= runtime->boundary) { + if (params->silence_threshold != 0) + return -EINVAL; + } else { + if (params->silence_size > params->silence_threshold) + return -EINVAL; + if (params->silence_threshold > runtime->buffer_size) + return -EINVAL; + } + snd_pcm_stream_lock_irq(substream); + runtime->tstamp_mode = params->tstamp_mode; + runtime->sleep_min = params->sleep_min; + runtime->period_step = params->period_step; + runtime->control->avail_min = params->avail_min; + runtime->start_threshold = params->start_threshold; + runtime->stop_threshold = params->stop_threshold; + runtime->silence_threshold = params->silence_threshold; + runtime->silence_size = params->silence_size; + runtime->xfer_align = params->xfer_align; + params->boundary = runtime->boundary; + if (snd_pcm_running(substream)) { + if (runtime->sleep_min) + snd_pcm_tick_prepare(substream); + else + snd_pcm_tick_set(substream, 0); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, ULONG_MAX); + wake_up(&runtime->sleep); + } + snd_pcm_stream_unlock_irq(substream); + return 0; +} + +static int snd_pcm_sw_params_user(snd_pcm_substream_t * substream, snd_pcm_sw_params_t __user * _params) +{ + snd_pcm_sw_params_t params; + int err; + if (copy_from_user(¶ms, _params, sizeof(params))) + return -EFAULT; + err = snd_pcm_sw_params(substream, ¶ms); + if (copy_to_user(_params, ¶ms, sizeof(params))) + return -EFAULT; + return err; +} + +int snd_pcm_status(snd_pcm_substream_t *substream, + snd_pcm_status_t *status) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + + snd_pcm_stream_lock_irq(substream); + status->state = runtime->status->state; + status->suspended_state = runtime->status->suspended_state; + if (status->state == SNDRV_PCM_STATE_OPEN) + goto _end; + status->trigger_tstamp = runtime->trigger_tstamp; + if (snd_pcm_running(substream)) { + snd_pcm_update_hw_ptr(substream); + if (runtime->tstamp_mode & SNDRV_PCM_TSTAMP_MMAP) + status->tstamp = runtime->status->tstamp; + else + snd_timestamp_now(&status->tstamp, runtime->tstamp_timespec); + } else + snd_timestamp_now(&status->tstamp, runtime->tstamp_timespec); + status->appl_ptr = runtime->control->appl_ptr; + status->hw_ptr = runtime->status->hw_ptr; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + status->avail = snd_pcm_playback_avail(runtime); + if (runtime->status->state == SNDRV_PCM_STATE_RUNNING || + runtime->status->state == SNDRV_PCM_STATE_DRAINING) + status->delay = runtime->buffer_size - status->avail; + else + status->delay = 0; + } else { + status->avail = snd_pcm_capture_avail(runtime); + if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) + status->delay = status->avail; + else + status->delay = 0; + } + status->avail_max = runtime->avail_max; + status->overrange = runtime->overrange; + runtime->avail_max = 0; + runtime->overrange = 0; + _end: + snd_pcm_stream_unlock_irq(substream); + return 0; +} + +static int snd_pcm_status_user(snd_pcm_substream_t * substream, snd_pcm_status_t __user * _status) +{ + snd_pcm_status_t status; + snd_pcm_runtime_t *runtime; + int res; + + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + memset(&status, 0, sizeof(status)); + res = snd_pcm_status(substream, &status); + if (res < 0) + return res; + if (copy_to_user(_status, &status, sizeof(status))) + return -EFAULT; + return 0; +} + +static int snd_pcm_channel_info(snd_pcm_substream_t * substream, snd_pcm_channel_info_t * info) +{ + snd_pcm_runtime_t *runtime; + unsigned int channel; + + snd_assert(substream != NULL, return -ENXIO); + channel = info->channel; + runtime = substream->runtime; + snd_pcm_stream_lock_irq(substream); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) { + snd_pcm_stream_unlock_irq(substream); + return -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); + if (channel >= runtime->channels) + return -EINVAL; + memset(info, 0, sizeof(*info)); + info->channel = channel; + return substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info); +} + +static int snd_pcm_channel_info_user(snd_pcm_substream_t * substream, snd_pcm_channel_info_t __user * _info) +{ + snd_pcm_channel_info_t info; + int res; + + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + res = snd_pcm_channel_info(substream, &info); + if (res < 0) + return res; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static void snd_pcm_trigger_tstamp(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (runtime->trigger_master == NULL) + return; + if (runtime->trigger_master == substream) { + snd_timestamp_now(&runtime->trigger_tstamp, runtime->tstamp_timespec); + } else { + snd_pcm_trigger_tstamp(runtime->trigger_master); + runtime->trigger_tstamp = runtime->trigger_master->runtime->trigger_tstamp; + } + runtime->trigger_master = NULL; +} + +struct action_ops { + int (*pre_action)(snd_pcm_substream_t *substream, int state); + int (*do_action)(snd_pcm_substream_t *substream, int state); + void (*undo_action)(snd_pcm_substream_t *substream, int state); + void (*post_action)(snd_pcm_substream_t *substream, int state); +}; + +/* + * this functions is core for handling of linked stream + * Note: the stream state might be changed also on failure + * Note2: call with calling stream lock + link lock + */ +static int snd_pcm_action_group(struct action_ops *ops, + snd_pcm_substream_t *substream, + int state, int do_lock) +{ + struct list_head *pos; + snd_pcm_substream_t *s = NULL; + snd_pcm_substream_t *s1; + int res = 0; + + snd_pcm_group_for_each(pos, substream) { + s = snd_pcm_group_substream_entry(pos); + if (do_lock && s != substream) + spin_lock(&s->self_group.lock); + res = ops->pre_action(s, state); + if (res < 0) + goto _unlock; + } + snd_pcm_group_for_each(pos, substream) { + s = snd_pcm_group_substream_entry(pos); + res = ops->do_action(s, state); + if (res < 0) { + if (ops->undo_action) { + snd_pcm_group_for_each(pos, substream) { + s1 = snd_pcm_group_substream_entry(pos); + if (s1 == s) /* failed stream */ + break; + ops->undo_action(s1, state); + } + } + s = NULL; /* unlock all */ + goto _unlock; + } + } + snd_pcm_group_for_each(pos, substream) { + s = snd_pcm_group_substream_entry(pos); + ops->post_action(s, state); + } + _unlock: + if (do_lock) { + /* unlock streams */ + snd_pcm_group_for_each(pos, substream) { + s1 = snd_pcm_group_substream_entry(pos); + if (s1 != substream) + spin_unlock(&s1->self_group.lock); + if (s1 == s) /* end */ + break; + } + } + return res; +} + +/* + * Note: call with stream lock + */ +static int snd_pcm_action_single(struct action_ops *ops, + snd_pcm_substream_t *substream, + int state) +{ + int res; + + res = ops->pre_action(substream, state); + if (res < 0) + return res; + res = ops->do_action(substream, state); + if (res == 0) + ops->post_action(substream, state); + else if (ops->undo_action) + ops->undo_action(substream, state); + return res; +} + +/* + * Note: call with stream lock + */ +static int snd_pcm_action(struct action_ops *ops, + snd_pcm_substream_t *substream, + int state) +{ + int res; + + if (snd_pcm_stream_linked(substream)) { + if (!spin_trylock(&substream->group->lock)) { + spin_unlock(&substream->self_group.lock); + spin_lock(&substream->group->lock); + spin_lock(&substream->self_group.lock); + } + res = snd_pcm_action_group(ops, substream, state, 1); + spin_unlock(&substream->group->lock); + } else { + res = snd_pcm_action_single(ops, substream, state); + } + return res; +} + +/* + * Note: don't use any locks before + */ +static int snd_pcm_action_lock_irq(struct action_ops *ops, + snd_pcm_substream_t *substream, + int state) +{ + int res; + + read_lock_irq(&snd_pcm_link_rwlock); + if (snd_pcm_stream_linked(substream)) { + spin_lock(&substream->group->lock); + spin_lock(&substream->self_group.lock); + res = snd_pcm_action_group(ops, substream, state, 1); + spin_unlock(&substream->self_group.lock); + spin_unlock(&substream->group->lock); + } else { + spin_lock(&substream->self_group.lock); + res = snd_pcm_action_single(ops, substream, state); + spin_unlock(&substream->self_group.lock); + } + read_unlock_irq(&snd_pcm_link_rwlock); + return res; +} + +/* + */ +static int snd_pcm_action_nonatomic(struct action_ops *ops, + snd_pcm_substream_t *substream, + int state) +{ + int res; + + down_read(&snd_pcm_link_rwsem); + if (snd_pcm_stream_linked(substream)) + res = snd_pcm_action_group(ops, substream, state, 0); + else + res = snd_pcm_action_single(ops, substream, state); + up_read(&snd_pcm_link_rwsem); + return res; +} + +/* + * start callbacks + */ +static int snd_pcm_pre_start(snd_pcm_substream_t *substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (runtime->status->state != SNDRV_PCM_STATE_PREPARED) + return -EBADFD; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + !snd_pcm_playback_data(substream)) + return -EPIPE; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_start(snd_pcm_substream_t *substream, int state) +{ + if (substream->runtime->trigger_master != substream) + return 0; + return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START); +} + +static void snd_pcm_undo_start(snd_pcm_substream_t *substream, int state) +{ + if (substream->runtime->trigger_master == substream) + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP); +} + +static void snd_pcm_post_start(snd_pcm_substream_t *substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + runtime->status->state = state; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, ULONG_MAX); + if (runtime->sleep_min) + snd_pcm_tick_prepare(substream); + if (substream->timer) + snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTART, &runtime->trigger_tstamp); +} + +static struct action_ops snd_pcm_action_start = { + .pre_action = snd_pcm_pre_start, + .do_action = snd_pcm_do_start, + .undo_action = snd_pcm_undo_start, + .post_action = snd_pcm_post_start +}; + +/** + * snd_pcm_start + * + * Start all linked streams. + */ +int snd_pcm_start(snd_pcm_substream_t *substream) +{ + return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING); +} + +/* + * stop callbacks + */ +static int snd_pcm_pre_stop(snd_pcm_substream_t *substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_stop(snd_pcm_substream_t *substream, int state) +{ + if (substream->runtime->trigger_master == substream && + snd_pcm_running(substream)) + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP); + return 0; /* unconditonally stop all substreams */ +} + +static void snd_pcm_post_stop(snd_pcm_substream_t *substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (runtime->status->state != state) { + snd_pcm_trigger_tstamp(substream); + if (substream->timer) + snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTOP, &runtime->trigger_tstamp); + runtime->status->state = state; + snd_pcm_tick_set(substream, 0); + } + wake_up(&runtime->sleep); +} + +static struct action_ops snd_pcm_action_stop = { + .pre_action = snd_pcm_pre_stop, + .do_action = snd_pcm_do_stop, + .post_action = snd_pcm_post_stop +}; + +/** + * snd_pcm_stop + * + * Try to stop all running streams in the substream group. + * The state of each stream is changed to the given value after that unconditionally. + */ +int snd_pcm_stop(snd_pcm_substream_t *substream, int state) +{ + return snd_pcm_action(&snd_pcm_action_stop, substream, state); +} + +/** + * snd_pcm_drain_done + * + * Stop the DMA only when the given stream is playback. + * The state is changed to SETUP. + * Unlike snd_pcm_stop(), this affects only the given stream. + */ +int snd_pcm_drain_done(snd_pcm_substream_t *substream) +{ + return snd_pcm_action_single(&snd_pcm_action_stop, substream, SNDRV_PCM_STATE_SETUP); +} + +/* + * pause callbacks + */ +static int snd_pcm_pre_pause(snd_pcm_substream_t *substream, int push) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (!(runtime->info & SNDRV_PCM_INFO_PAUSE)) + return -ENOSYS; + if (push) { + if (runtime->status->state != SNDRV_PCM_STATE_RUNNING) + return -EBADFD; + } else if (runtime->status->state != SNDRV_PCM_STATE_PAUSED) + return -EBADFD; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_pause(snd_pcm_substream_t *substream, int push) +{ + if (substream->runtime->trigger_master != substream) + return 0; + return substream->ops->trigger(substream, + push ? SNDRV_PCM_TRIGGER_PAUSE_PUSH : + SNDRV_PCM_TRIGGER_PAUSE_RELEASE); +} + +static void snd_pcm_undo_pause(snd_pcm_substream_t *substream, int push) +{ + if (substream->runtime->trigger_master == substream) + substream->ops->trigger(substream, + push ? SNDRV_PCM_TRIGGER_PAUSE_RELEASE : + SNDRV_PCM_TRIGGER_PAUSE_PUSH); +} + +static void snd_pcm_post_pause(snd_pcm_substream_t *substream, int push) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + if (push) { + runtime->status->state = SNDRV_PCM_STATE_PAUSED; + if (substream->timer) + snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MPAUSE, &runtime->trigger_tstamp); + snd_pcm_tick_set(substream, 0); + wake_up(&runtime->sleep); + } else { + runtime->status->state = SNDRV_PCM_STATE_RUNNING; + if (runtime->sleep_min) + snd_pcm_tick_prepare(substream); + if (substream->timer) + snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MCONTINUE, &runtime->trigger_tstamp); + } +} + +static struct action_ops snd_pcm_action_pause = { + .pre_action = snd_pcm_pre_pause, + .do_action = snd_pcm_do_pause, + .undo_action = snd_pcm_undo_pause, + .post_action = snd_pcm_post_pause +}; + +/* + * Push/release the pause for all linked streams. + */ +static int snd_pcm_pause(snd_pcm_substream_t *substream, int push) +{ + return snd_pcm_action(&snd_pcm_action_pause, substream, push); +} + +#ifdef CONFIG_PM +/* suspend */ + +static int snd_pcm_pre_suspend(snd_pcm_substream_t *substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) + return -EBUSY; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_suspend(snd_pcm_substream_t *substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (runtime->trigger_master != substream) + return 0; + if (! snd_pcm_running(substream)) + return 0; + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND); + return 0; /* suspend unconditionally */ +} + +static void snd_pcm_post_suspend(snd_pcm_substream_t *substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + if (substream->timer) + snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MPAUSE, &runtime->trigger_tstamp); + runtime->status->suspended_state = runtime->status->state; + runtime->status->state = SNDRV_PCM_STATE_SUSPENDED; + snd_pcm_tick_set(substream, 0); + wake_up(&runtime->sleep); +} + +static struct action_ops snd_pcm_action_suspend = { + .pre_action = snd_pcm_pre_suspend, + .do_action = snd_pcm_do_suspend, + .post_action = snd_pcm_post_suspend +}; + +/** + * snd_pcm_suspend + * + * Trigger SUSPEND to all linked streams. + * After this call, all streams are changed to SUSPENDED state. + */ +int snd_pcm_suspend(snd_pcm_substream_t *substream) +{ + int err; + unsigned long flags; + + snd_pcm_stream_lock_irqsave(substream, flags); + err = snd_pcm_action(&snd_pcm_action_suspend, substream, 0); + snd_pcm_stream_unlock_irqrestore(substream, flags); + return err; +} + +/** + * snd_pcm_suspend_all + * + * Trigger SUSPEND to all substreams in the given pcm. + * After this call, all streams are changed to SUSPENDED state. + */ +int snd_pcm_suspend_all(snd_pcm_t *pcm) +{ + snd_pcm_substream_t *substream; + int stream, err = 0; + + for (stream = 0; stream < 2; stream++) { + for (substream = pcm->streams[stream].substream; substream; substream = substream->next) { + /* FIXME: the open/close code should lock this as well */ + if (substream->runtime == NULL) + continue; + err = snd_pcm_suspend(substream); + if (err < 0 && err != -EBUSY) + return err; + } + } + return 0; +} + +/* resume */ + +static int snd_pcm_pre_resume(snd_pcm_substream_t *substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (!(runtime->info & SNDRV_PCM_INFO_RESUME)) + return -ENOSYS; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_resume(snd_pcm_substream_t *substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (runtime->trigger_master != substream) + return 0; + /* DMA not running previously? */ + if (runtime->status->suspended_state != SNDRV_PCM_STATE_RUNNING && + (runtime->status->suspended_state != SNDRV_PCM_STATE_DRAINING || + substream->stream != SNDRV_PCM_STREAM_PLAYBACK)) + return 0; + return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_RESUME); +} + +static void snd_pcm_undo_resume(snd_pcm_substream_t *substream, int state) +{ + if (substream->runtime->trigger_master == substream && + snd_pcm_running(substream)) + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND); +} + +static void snd_pcm_post_resume(snd_pcm_substream_t *substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + if (substream->timer) + snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MCONTINUE, &runtime->trigger_tstamp); + runtime->status->state = runtime->status->suspended_state; + if (runtime->sleep_min) + snd_pcm_tick_prepare(substream); +} + +static struct action_ops snd_pcm_action_resume = { + .pre_action = snd_pcm_pre_resume, + .do_action = snd_pcm_do_resume, + .undo_action = snd_pcm_undo_resume, + .post_action = snd_pcm_post_resume +}; + +static int snd_pcm_resume(snd_pcm_substream_t *substream) +{ + snd_card_t *card = substream->pcm->card; + int res; + + snd_power_lock(card); + if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile)) >= 0) + res = snd_pcm_action_lock_irq(&snd_pcm_action_resume, substream, 0); + snd_power_unlock(card); + return res; +} + +#else + +static int snd_pcm_resume(snd_pcm_substream_t *substream) +{ + return -ENOSYS; +} + +#endif /* CONFIG_PM */ + +/* + * xrun ioctl + * + * Change the RUNNING stream(s) to XRUN state. + */ +static int snd_pcm_xrun(snd_pcm_substream_t *substream) +{ + snd_card_t *card = substream->pcm->card; + snd_pcm_runtime_t *runtime = substream->runtime; + int result; + + snd_power_lock(card); + if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { + result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile); + if (result < 0) + goto _unlock; + } + + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_XRUN: + result = 0; /* already there */ + break; + case SNDRV_PCM_STATE_RUNNING: + result = snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + break; + default: + result = -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); + _unlock: + snd_power_unlock(card); + return result; +} + +/* + * reset ioctl + */ +static int snd_pcm_pre_reset(snd_pcm_substream_t * substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + switch (runtime->status->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + case SNDRV_PCM_STATE_SUSPENDED: + return 0; + default: + return -EBADFD; + } +} + +static int snd_pcm_do_reset(snd_pcm_substream_t * substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL); + if (err < 0) + return err; + // snd_assert(runtime->status->hw_ptr < runtime->buffer_size, ); + runtime->hw_ptr_base = 0; + runtime->hw_ptr_interrupt = runtime->status->hw_ptr - runtime->status->hw_ptr % runtime->period_size; + runtime->silence_start = runtime->status->hw_ptr; + runtime->silence_filled = 0; + return 0; +} + +static void snd_pcm_post_reset(snd_pcm_substream_t * substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + runtime->control->appl_ptr = runtime->status->hw_ptr; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, ULONG_MAX); +} + +static struct action_ops snd_pcm_action_reset = { + .pre_action = snd_pcm_pre_reset, + .do_action = snd_pcm_do_reset, + .post_action = snd_pcm_post_reset +}; + +static int snd_pcm_reset(snd_pcm_substream_t *substream) +{ + return snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream, 0); +} + +/* + * prepare ioctl + */ +static int snd_pcm_pre_prepare(snd_pcm_substream_t * substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (snd_pcm_running(substream)) + return -EBUSY; + return 0; +} + +static int snd_pcm_do_prepare(snd_pcm_substream_t * substream, int state) +{ + int err; + err = substream->ops->prepare(substream); + if (err < 0) + return err; + return snd_pcm_do_reset(substream, 0); +} + +static void snd_pcm_post_prepare(snd_pcm_substream_t * substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + runtime->control->appl_ptr = runtime->status->hw_ptr; + runtime->status->state = SNDRV_PCM_STATE_PREPARED; +} + +static struct action_ops snd_pcm_action_prepare = { + .pre_action = snd_pcm_pre_prepare, + .do_action = snd_pcm_do_prepare, + .post_action = snd_pcm_post_prepare +}; + +/** + * snd_pcm_prepare + */ +int snd_pcm_prepare(snd_pcm_substream_t *substream) +{ + int res; + snd_card_t *card = substream->pcm->card; + + snd_power_lock(card); + if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile)) >= 0) + res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare, substream, 0); + snd_power_unlock(card); + return res; +} + +/* + * drain ioctl + */ + +static int snd_pcm_pre_drain_init(snd_pcm_substream_t * substream, int state) +{ + if (substream->ffile->f_flags & O_NONBLOCK) + return -EAGAIN; + substream->runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_drain_init(snd_pcm_substream_t * substream, int state) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (runtime->status->state) { + case SNDRV_PCM_STATE_PREPARED: + /* start playback stream if possible */ + if (! snd_pcm_playback_empty(substream)) { + snd_pcm_do_start(substream, SNDRV_PCM_STATE_DRAINING); + snd_pcm_post_start(substream, SNDRV_PCM_STATE_DRAINING); + } + break; + case SNDRV_PCM_STATE_RUNNING: + runtime->status->state = SNDRV_PCM_STATE_DRAINING; + break; + default: + break; + } + } else { + /* stop running stream */ + if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) { + int state = snd_pcm_capture_avail(runtime) > 0 ? + SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP; + snd_pcm_do_stop(substream, state); + snd_pcm_post_stop(substream, state); + } + } + return 0; +} + +static void snd_pcm_post_drain_init(snd_pcm_substream_t * substream, int state) +{ +} + +static struct action_ops snd_pcm_action_drain_init = { + .pre_action = snd_pcm_pre_drain_init, + .do_action = snd_pcm_do_drain_init, + .post_action = snd_pcm_post_drain_init +}; + +struct drain_rec { + snd_pcm_substream_t *substream; + wait_queue_t wait; + snd_pcm_uframes_t stop_threshold; +}; + +static int snd_pcm_drop(snd_pcm_substream_t *substream); + +/* + * Drain the stream(s). + * When the substream is linked, sync until the draining of all playback streams + * is finished. + * After this call, all streams are supposed to be either SETUP or DRAINING + * (capture only) state. + */ +static int snd_pcm_drain(snd_pcm_substream_t *substream) +{ + snd_card_t *card; + snd_pcm_runtime_t *runtime; + struct list_head *pos; + int result = 0; + int i, num_drecs; + struct drain_rec *drec, drec_tmp, *d; + + snd_assert(substream != NULL, return -ENXIO); + card = substream->pcm->card; + runtime = substream->runtime; + + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + down_read(&snd_pcm_link_rwsem); + snd_power_lock(card); + if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { + result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile); + if (result < 0) + goto _unlock; + } + + /* allocate temporary record for drain sync */ + if (snd_pcm_stream_linked(substream)) { + drec = kmalloc(substream->group->count * sizeof(*drec), GFP_KERNEL); + if (! drec) { + result = -ENOMEM; + goto _unlock; + } + } else + drec = &drec_tmp; + + snd_pcm_stream_lock_irq(substream); + /* resume pause */ + if (runtime->status->state == SNDRV_PCM_STATE_PAUSED) + snd_pcm_pause(substream, 0); + + /* pre-start/stop - all running streams are changed to DRAINING state */ + result = snd_pcm_action(&snd_pcm_action_drain_init, substream, 0); + if (result < 0) + goto _end; + + /* check streams with PLAYBACK & DRAINING */ + num_drecs = 0; + snd_pcm_group_for_each(pos, substream) { + snd_pcm_substream_t *s = snd_pcm_group_substream_entry(pos); + runtime = s->runtime; + if (runtime->status->state != SNDRV_PCM_STATE_DRAINING) { + runtime->status->state = SNDRV_PCM_STATE_SETUP; + continue; + } + if (s->stream == SNDRV_PCM_STREAM_PLAYBACK) { + d = &drec[num_drecs++]; + d->substream = s; + init_waitqueue_entry(&d->wait, current); + add_wait_queue(&runtime->sleep, &d->wait); + /* stop_threshold fixup to avoid endless loop when + * stop_threshold > buffer_size + */ + d->stop_threshold = runtime->stop_threshold; + if (runtime->stop_threshold > runtime->buffer_size) + runtime->stop_threshold = runtime->buffer_size; + } + } + + if (! num_drecs) + goto _end; + + for (;;) { + long tout; + if (signal_pending(current)) { + result = -ERESTARTSYS; + break; + } + set_current_state(TASK_INTERRUPTIBLE); + snd_pcm_stream_unlock_irq(substream); + snd_power_unlock(card); + tout = schedule_timeout(10 * HZ); + snd_power_lock(card); + snd_pcm_stream_lock_irq(substream); + if (tout == 0) { + if (substream->runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) + result = -ESTRPIPE; + else { + snd_printd("playback drain error (DMA or IRQ trouble?)\n"); + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + result = -EIO; + } + break; + } + /* all finished? */ + for (i = 0; i < num_drecs; i++) { + runtime = drec[i].substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) + break; + } + if (i == num_drecs) + break; + } + for (i = 0; i < num_drecs; i++) { + d = &drec[i]; + runtime = d->substream->runtime; + remove_wait_queue(&runtime->sleep, &d->wait); + runtime->stop_threshold = d->stop_threshold; + } + + _end: + snd_pcm_stream_unlock_irq(substream); + if (drec != &drec_tmp) + kfree(drec); + _unlock: + snd_power_unlock(card); + up_read(&snd_pcm_link_rwsem); + + return result; +} + +/* + * drop ioctl + * + * Immediately put all linked substreams into SETUP state. + */ +static int snd_pcm_drop(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime; + snd_card_t *card; + int result = 0; + + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + card = substream->pcm->card; + + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + snd_power_lock(card); + if (runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { + result = snd_power_wait(card, SNDRV_CTL_POWER_D0, substream->ffile); + if (result < 0) + goto _unlock; + } + + snd_pcm_stream_lock_irq(substream); + /* resume pause */ + if (runtime->status->state == SNDRV_PCM_STATE_PAUSED) + snd_pcm_pause(substream, 0); + + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + snd_pcm_stream_unlock_irq(substream); + _unlock: + snd_power_unlock(card); + return result; +} + + +/* WARNING: Don't forget to fput back the file */ +extern int snd_major; +static struct file *snd_pcm_file_fd(int fd) +{ + struct file *file; + struct inode *inode; + unsigned short minor; + file = fget(fd); + if (!file) + return NULL; + inode = file->f_dentry->d_inode; + if (!S_ISCHR(inode->i_mode) || + imajor(inode) != snd_major) { + fput(file); + return NULL; + } + minor = iminor(inode); + if (minor >= 256 || + minor % SNDRV_MINOR_DEVICES < SNDRV_MINOR_PCM_PLAYBACK) { + fput(file); + return NULL; + } + return file; +} + +/* + * PCM link handling + */ +static int snd_pcm_link(snd_pcm_substream_t *substream, int fd) +{ + int res = 0; + struct file *file; + snd_pcm_file_t *pcm_file; + snd_pcm_substream_t *substream1; + + file = snd_pcm_file_fd(fd); + if (!file) + return -EBADFD; + pcm_file = file->private_data; + substream1 = pcm_file->substream; + down_write(&snd_pcm_link_rwsem); + write_lock_irq(&snd_pcm_link_rwlock); + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN || + substream->runtime->status->state != substream1->runtime->status->state) { + res = -EBADFD; + goto _end; + } + if (snd_pcm_stream_linked(substream1)) { + res = -EALREADY; + goto _end; + } + if (!snd_pcm_stream_linked(substream)) { + substream->group = kmalloc(sizeof(snd_pcm_group_t), GFP_ATOMIC); + if (substream->group == NULL) { + res = -ENOMEM; + goto _end; + } + spin_lock_init(&substream->group->lock); + INIT_LIST_HEAD(&substream->group->substreams); + list_add_tail(&substream->link_list, &substream->group->substreams); + substream->group->count = 1; + } + list_add_tail(&substream1->link_list, &substream->group->substreams); + substream->group->count++; + substream1->group = substream->group; + _end: + write_unlock_irq(&snd_pcm_link_rwlock); + up_write(&snd_pcm_link_rwsem); + fput(file); + return res; +} + +static void relink_to_local(snd_pcm_substream_t *substream) +{ + substream->group = &substream->self_group; + INIT_LIST_HEAD(&substream->self_group.substreams); + list_add_tail(&substream->link_list, &substream->self_group.substreams); +} + +static int snd_pcm_unlink(snd_pcm_substream_t *substream) +{ + struct list_head *pos; + int res = 0; + + down_write(&snd_pcm_link_rwsem); + write_lock_irq(&snd_pcm_link_rwlock); + if (!snd_pcm_stream_linked(substream)) { + res = -EALREADY; + goto _end; + } + list_del(&substream->link_list); + substream->group->count--; + if (substream->group->count == 1) { /* detach the last stream, too */ + snd_pcm_group_for_each(pos, substream) { + relink_to_local(snd_pcm_group_substream_entry(pos)); + break; + } + kfree(substream->group); + } + relink_to_local(substream); + _end: + write_unlock_irq(&snd_pcm_link_rwlock); + up_write(&snd_pcm_link_rwsem); + return res; +} + +/* + * hw configurator + */ +static int snd_pcm_hw_rule_mul(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_interval_t t; + snd_interval_mul(hw_param_interval_c(params, rule->deps[0]), + hw_param_interval_c(params, rule->deps[1]), &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_div(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_interval_t t; + snd_interval_div(hw_param_interval_c(params, rule->deps[0]), + hw_param_interval_c(params, rule->deps[1]), &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_muldivk(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_interval_t t; + snd_interval_muldivk(hw_param_interval_c(params, rule->deps[0]), + hw_param_interval_c(params, rule->deps[1]), + (unsigned long) rule->private, &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_mulkdiv(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_interval_t t; + snd_interval_mulkdiv(hw_param_interval_c(params, rule->deps[0]), + (unsigned long) rule->private, + hw_param_interval_c(params, rule->deps[1]), &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_format(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + unsigned int k; + snd_interval_t *i = hw_param_interval(params, rule->deps[0]); + snd_mask_t m; + snd_mask_t *mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_any(&m); + for (k = 0; k <= SNDRV_PCM_FORMAT_LAST; ++k) { + int bits; + if (! snd_mask_test(mask, k)) + continue; + bits = snd_pcm_format_physical_width(k); + if (bits <= 0) + continue; /* ignore invalid formats */ + if ((unsigned)bits < i->min || (unsigned)bits > i->max) + snd_mask_reset(&m, k); + } + return snd_mask_refine(mask, &m); +} + +static int snd_pcm_hw_rule_sample_bits(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_interval_t t; + unsigned int k; + t.min = UINT_MAX; + t.max = 0; + t.openmin = 0; + t.openmax = 0; + for (k = 0; k <= SNDRV_PCM_FORMAT_LAST; ++k) { + int bits; + if (! snd_mask_test(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), k)) + continue; + bits = snd_pcm_format_physical_width(k); + if (bits <= 0) + continue; /* ignore invalid formats */ + if (t.min > (unsigned)bits) + t.min = bits; + if (t.max < (unsigned)bits) + t.max = bits; + } + t.integer = 1; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12 +#error "Change this table" +#endif + +static unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100, + 48000, 64000, 88200, 96000, 176400, 192000 }; + +static int snd_pcm_hw_rule_rate(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_pcm_hardware_t *hw = rule->private; + return snd_interval_list(hw_param_interval(params, rule->var), + ARRAY_SIZE(rates), rates, hw->rates); +} + +static int snd_pcm_hw_rule_buffer_bytes_max(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_interval_t t; + snd_pcm_substream_t *substream = rule->private; + t.min = 0; + t.max = substream->buffer_bytes_max; + t.openmin = 0; + t.openmax = 0; + t.integer = 1; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +int snd_pcm_hw_constraints_init(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_hw_constraints_t *constrs = &runtime->hw_constraints; + int k, err; + + for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) { + snd_mask_any(constrs_mask(constrs, k)); + } + + for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) { + snd_interval_any(constrs_interval(constrs, k)); + } + + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS)); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + snd_pcm_hw_rule_format, NULL, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + snd_pcm_hw_rule_sample_bits, NULL, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, + snd_pcm_hw_rule_mul, NULL, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_TIME, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_BUFFER_TIME, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + snd_pcm_hw_rule_muldivk, (void*) 1000000, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + snd_pcm_hw_rule_mul, NULL, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + snd_pcm_hw_rule_muldivk, (void*) 1000000, + SNDRV_PCM_HW_PARAM_BUFFER_TIME, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + snd_pcm_hw_rule_muldivk, (void*) 8, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + snd_pcm_hw_rule_muldivk, (void*) 8, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_TIME, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_TIME, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + return 0; +} + +int snd_pcm_hw_constraints_complete(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_hardware_t *hw = &runtime->hw; + int err; + unsigned int mask = 0; + + if (hw->info & SNDRV_PCM_INFO_INTERLEAVED) + mask |= 1 << SNDRV_PCM_ACCESS_RW_INTERLEAVED; + if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED) + mask |= 1 << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; + if (hw->info & SNDRV_PCM_INFO_MMAP) { + if (hw->info & SNDRV_PCM_INFO_INTERLEAVED) + mask |= 1 << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED; + if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED) + mask |= 1 << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED; + if (hw->info & SNDRV_PCM_INFO_COMPLEX) + mask |= 1 << SNDRV_PCM_ACCESS_MMAP_COMPLEX; + } + err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask); + snd_assert(err >= 0, return -EINVAL); + + err = snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, hw->formats); + snd_assert(err >= 0, return -EINVAL); + + err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_SUBFORMAT, 1 << SNDRV_PCM_SUBFORMAT_STD); + snd_assert(err >= 0, return -EINVAL); + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, + hw->channels_min, hw->channels_max); + snd_assert(err >= 0, return -EINVAL); + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, + hw->rate_min, hw->rate_max); + snd_assert(err >= 0, return -EINVAL); + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + hw->period_bytes_min, hw->period_bytes_max); + snd_assert(err >= 0, return -EINVAL); + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIODS, + hw->periods_min, hw->periods_max); + snd_assert(err >= 0, return -EINVAL); + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + hw->period_bytes_min, hw->buffer_bytes_max); + snd_assert(err >= 0, return -EINVAL); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + snd_pcm_hw_rule_buffer_bytes_max, substream, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, -1); + if (err < 0) + return err; + + /* FIXME: remove */ + if (runtime->dma_bytes) { + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, runtime->dma_bytes); + snd_assert(err >= 0, return -EINVAL); + } + + if (!(hw->rates & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))) { + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_rate, hw, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + } + + /* FIXME: this belong to lowlevel */ + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_TICK_TIME, + 1000000 / HZ, 1000000 / HZ); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + + return 0; +} + +static void snd_pcm_add_file(snd_pcm_str_t *str, + snd_pcm_file_t *pcm_file) +{ + pcm_file->next = str->files; + str->files = pcm_file; +} + +static void snd_pcm_remove_file(snd_pcm_str_t *str, + snd_pcm_file_t *pcm_file) +{ + snd_pcm_file_t * pcm_file1; + if (str->files == pcm_file) { + str->files = pcm_file->next; + } else { + pcm_file1 = str->files; + while (pcm_file1 && pcm_file1->next != pcm_file) + pcm_file1 = pcm_file1->next; + if (pcm_file1 != NULL) + pcm_file1->next = pcm_file->next; + } +} + +static int snd_pcm_release_file(snd_pcm_file_t * pcm_file) +{ + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + snd_pcm_str_t * str; + + snd_assert(pcm_file != NULL, return -ENXIO); + substream = pcm_file->substream; + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + str = substream->pstr; + snd_pcm_unlink(substream); + if (substream->open_flag) { + if (substream->ops->hw_free != NULL) + substream->ops->hw_free(substream); + substream->ops->close(substream); + substream->open_flag = 0; + } + substream->ffile = NULL; + snd_pcm_remove_file(str, pcm_file); + snd_pcm_release_substream(substream); + kfree(pcm_file); + return 0; +} + +static int snd_pcm_open_file(struct file *file, + snd_pcm_t *pcm, + int stream, + snd_pcm_file_t **rpcm_file) +{ + int err = 0; + snd_pcm_file_t *pcm_file; + snd_pcm_substream_t *substream; + snd_pcm_str_t *str; + + snd_assert(rpcm_file != NULL, return -EINVAL); + *rpcm_file = NULL; + + pcm_file = kcalloc(1, sizeof(*pcm_file), GFP_KERNEL); + if (pcm_file == NULL) { + return -ENOMEM; + } + + if ((err = snd_pcm_open_substream(pcm, stream, &substream)) < 0) { + kfree(pcm_file); + return err; + } + + str = substream->pstr; + substream->file = pcm_file; + substream->no_mmap_ctrl = 0; + + pcm_file->substream = substream; + + snd_pcm_add_file(str, pcm_file); + + err = snd_pcm_hw_constraints_init(substream); + if (err < 0) { + snd_printd("snd_pcm_hw_constraints_init failed\n"); + snd_pcm_release_file(pcm_file); + return err; + } + + if ((err = substream->ops->open(substream)) < 0) { + snd_pcm_release_file(pcm_file); + return err; + } + substream->open_flag = 1; + + err = snd_pcm_hw_constraints_complete(substream); + if (err < 0) { + snd_printd("snd_pcm_hw_constraints_complete failed\n"); + substream->ops->close(substream); + snd_pcm_release_file(pcm_file); + return err; + } + + substream->ffile = file; + + file->private_data = pcm_file; + *rpcm_file = pcm_file; + return 0; +} + +static int snd_pcm_open(struct inode *inode, struct file *file) +{ + int cardnum = SNDRV_MINOR_CARD(iminor(inode)); + int device = SNDRV_MINOR_DEVICE(iminor(inode)); + int err; + snd_pcm_t *pcm; + snd_pcm_file_t *pcm_file; + wait_queue_t wait; + + snd_runtime_check(device >= SNDRV_MINOR_PCM_PLAYBACK && device < SNDRV_MINOR_DEVICES, return -ENXIO); + pcm = snd_pcm_devices[(cardnum * SNDRV_PCM_DEVICES) + (device % SNDRV_MINOR_PCMS)]; + if (pcm == NULL) { + err = -ENODEV; + goto __error1; + } + err = snd_card_file_add(pcm->card, file); + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { + err = -EFAULT; + goto __error2; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&pcm->open_wait, &wait); + down(&pcm->open_mutex); + while (1) { + err = snd_pcm_open_file(file, pcm, device >= SNDRV_MINOR_PCM_CAPTURE ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, &pcm_file); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (file->f_flags & O_NONBLOCK) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + up(&pcm->open_mutex); + schedule(); + down(&pcm->open_mutex); + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&pcm->open_wait, &wait); + up(&pcm->open_mutex); + if (err < 0) + goto __error; + return err; + + __error: + module_put(pcm->card->module); + __error2: + snd_card_file_remove(pcm->card, file); + __error1: + return err; +} + +static int snd_pcm_release(struct inode *inode, struct file *file) +{ + snd_pcm_t *pcm; + snd_pcm_substream_t *substream; + snd_pcm_file_t *pcm_file; + + pcm_file = file->private_data; + substream = pcm_file->substream; + snd_assert(substream != NULL, return -ENXIO); + snd_assert(!atomic_read(&substream->runtime->mmap_count), ); + pcm = substream->pcm; + snd_pcm_drop(substream); + fasync_helper(-1, file, 0, &substream->runtime->fasync); + down(&pcm->open_mutex); + snd_pcm_release_file(pcm_file); + up(&pcm->open_mutex); + wake_up(&pcm->open_wait); + module_put(pcm->card->module); + snd_card_file_remove(pcm->card, file); + return 0; +} + +static snd_pcm_sframes_t snd_pcm_playback_rewind(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_sframes_t appl_ptr; + snd_pcm_sframes_t ret; + snd_pcm_sframes_t hw_avail; + + if (frames == 0) + return 0; + + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_PREPARED: + break; + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_RUNNING: + if (snd_pcm_update_hw_ptr(substream) >= 0) + break; + /* Fall through */ + case SNDRV_PCM_STATE_XRUN: + ret = -EPIPE; + goto __end; + default: + ret = -EBADFD; + goto __end; + } + + hw_avail = snd_pcm_playback_hw_avail(runtime); + if (hw_avail <= 0) { + ret = 0; + goto __end; + } + if (frames > (snd_pcm_uframes_t)hw_avail) + frames = hw_avail; + else + frames -= frames % runtime->xfer_align; + appl_ptr = runtime->control->appl_ptr - frames; + if (appl_ptr < 0) + appl_ptr += runtime->boundary; + runtime->control->appl_ptr = appl_ptr; + if (runtime->status->state == SNDRV_PCM_STATE_RUNNING && + runtime->sleep_min) + snd_pcm_tick_prepare(substream); + ret = frames; + __end: + snd_pcm_stream_unlock_irq(substream); + return ret; +} + +static snd_pcm_sframes_t snd_pcm_capture_rewind(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_sframes_t appl_ptr; + snd_pcm_sframes_t ret; + snd_pcm_sframes_t hw_avail; + + if (frames == 0) + return 0; + + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_DRAINING: + break; + case SNDRV_PCM_STATE_RUNNING: + if (snd_pcm_update_hw_ptr(substream) >= 0) + break; + /* Fall through */ + case SNDRV_PCM_STATE_XRUN: + ret = -EPIPE; + goto __end; + default: + ret = -EBADFD; + goto __end; + } + + hw_avail = snd_pcm_capture_hw_avail(runtime); + if (hw_avail <= 0) { + ret = 0; + goto __end; + } + if (frames > (snd_pcm_uframes_t)hw_avail) + frames = hw_avail; + else + frames -= frames % runtime->xfer_align; + appl_ptr = runtime->control->appl_ptr - frames; + if (appl_ptr < 0) + appl_ptr += runtime->boundary; + runtime->control->appl_ptr = appl_ptr; + if (runtime->status->state == SNDRV_PCM_STATE_RUNNING && + runtime->sleep_min) + snd_pcm_tick_prepare(substream); + ret = frames; + __end: + snd_pcm_stream_unlock_irq(substream); + return ret; +} + +static snd_pcm_sframes_t snd_pcm_playback_forward(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_sframes_t appl_ptr; + snd_pcm_sframes_t ret; + snd_pcm_sframes_t avail; + + if (frames == 0) + return 0; + + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + break; + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_RUNNING: + if (snd_pcm_update_hw_ptr(substream) >= 0) + break; + /* Fall through */ + case SNDRV_PCM_STATE_XRUN: + ret = -EPIPE; + goto __end; + default: + ret = -EBADFD; + goto __end; + } + + avail = snd_pcm_playback_avail(runtime); + if (avail <= 0) { + ret = 0; + goto __end; + } + if (frames > (snd_pcm_uframes_t)avail) + frames = avail; + else + frames -= frames % runtime->xfer_align; + appl_ptr = runtime->control->appl_ptr + frames; + if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary) + appl_ptr -= runtime->boundary; + runtime->control->appl_ptr = appl_ptr; + if (runtime->status->state == SNDRV_PCM_STATE_RUNNING && + runtime->sleep_min) + snd_pcm_tick_prepare(substream); + ret = frames; + __end: + snd_pcm_stream_unlock_irq(substream); + return ret; +} + +static snd_pcm_sframes_t snd_pcm_capture_forward(snd_pcm_substream_t *substream, snd_pcm_uframes_t frames) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_sframes_t appl_ptr; + snd_pcm_sframes_t ret; + snd_pcm_sframes_t avail; + + if (frames == 0) + return 0; + + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_PAUSED: + break; + case SNDRV_PCM_STATE_RUNNING: + if (snd_pcm_update_hw_ptr(substream) >= 0) + break; + /* Fall through */ + case SNDRV_PCM_STATE_XRUN: + ret = -EPIPE; + goto __end; + default: + ret = -EBADFD; + goto __end; + } + + avail = snd_pcm_capture_avail(runtime); + if (avail <= 0) { + ret = 0; + goto __end; + } + if (frames > (snd_pcm_uframes_t)avail) + frames = avail; + else + frames -= frames % runtime->xfer_align; + appl_ptr = runtime->control->appl_ptr + frames; + if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary) + appl_ptr -= runtime->boundary; + runtime->control->appl_ptr = appl_ptr; + if (runtime->status->state == SNDRV_PCM_STATE_RUNNING && + runtime->sleep_min) + snd_pcm_tick_prepare(substream); + ret = frames; + __end: + snd_pcm_stream_unlock_irq(substream); + return ret; +} + +static int snd_pcm_hwsync(snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_DRAINING: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + goto __badfd; + case SNDRV_PCM_STATE_RUNNING: + if ((err = snd_pcm_update_hw_ptr(substream)) < 0) + break; + /* Fall through */ + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_SUSPENDED: + err = 0; + break; + case SNDRV_PCM_STATE_XRUN: + err = -EPIPE; + break; + default: + __badfd: + err = -EBADFD; + break; + } + snd_pcm_stream_unlock_irq(substream); + return err; +} + +static int snd_pcm_delay(snd_pcm_substream_t *substream, snd_pcm_sframes_t __user *res) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + snd_pcm_sframes_t n = 0; + + snd_pcm_stream_lock_irq(substream); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_DRAINING: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + goto __badfd; + case SNDRV_PCM_STATE_RUNNING: + if ((err = snd_pcm_update_hw_ptr(substream)) < 0) + break; + /* Fall through */ + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_SUSPENDED: + err = 0; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + n = snd_pcm_playback_hw_avail(runtime); + else + n = snd_pcm_capture_avail(runtime); + break; + case SNDRV_PCM_STATE_XRUN: + err = -EPIPE; + break; + default: + __badfd: + err = -EBADFD; + break; + } + snd_pcm_stream_unlock_irq(substream); + if (!err) + if (put_user(n, res)) + err = -EFAULT; + return err; +} + +static int snd_pcm_sync_ptr(snd_pcm_substream_t *substream, struct sndrv_pcm_sync_ptr __user *_sync_ptr) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + struct sndrv_pcm_sync_ptr sync_ptr; + volatile struct sndrv_pcm_mmap_status *status; + volatile struct sndrv_pcm_mmap_control *control; + int err; + + memset(&sync_ptr, 0, sizeof(sync_ptr)); + if (get_user(sync_ptr.flags, (unsigned __user *)&(_sync_ptr->flags))) + return -EFAULT; + if (copy_from_user(&sync_ptr.c.control, &(_sync_ptr->c.control), sizeof(struct sndrv_pcm_mmap_control))) + return -EFAULT; + status = runtime->status; + control = runtime->control; + if (sync_ptr.flags & SNDRV_PCM_SYNC_PTR_HWSYNC) { + err = snd_pcm_hwsync(substream); + if (err < 0) + return err; + } + snd_pcm_stream_lock_irq(substream); + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) + control->appl_ptr = sync_ptr.c.control.appl_ptr; + else + sync_ptr.c.control.appl_ptr = control->appl_ptr; + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) + control->avail_min = sync_ptr.c.control.avail_min; + else + sync_ptr.c.control.avail_min = control->avail_min; + sync_ptr.s.status.state = status->state; + sync_ptr.s.status.hw_ptr = status->hw_ptr; + sync_ptr.s.status.tstamp = status->tstamp; + sync_ptr.s.status.suspended_state = status->suspended_state; + snd_pcm_stream_unlock_irq(substream); + if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr))) + return -EFAULT; + return 0; +} + +static int snd_pcm_playback_ioctl1(snd_pcm_substream_t *substream, + unsigned int cmd, void __user *arg); +static int snd_pcm_capture_ioctl1(snd_pcm_substream_t *substream, + unsigned int cmd, void __user *arg); + +static int snd_pcm_common_ioctl1(snd_pcm_substream_t *substream, + unsigned int cmd, void __user *arg) +{ + snd_assert(substream != NULL, return -ENXIO); + + switch (cmd) { + case SNDRV_PCM_IOCTL_PVERSION: + return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0; + case SNDRV_PCM_IOCTL_INFO: + return snd_pcm_info_user(substream, arg); + case SNDRV_PCM_IOCTL_TSTAMP: + { + int xarg; + if (get_user(xarg, (int __user *)arg)) + return -EFAULT; + substream->runtime->tstamp_timespec = xarg ? 1 : 0; + return 0; + } + case SNDRV_PCM_IOCTL_HW_REFINE: + return snd_pcm_hw_refine_user(substream, arg); + case SNDRV_PCM_IOCTL_HW_PARAMS: + return snd_pcm_hw_params_user(substream, arg); + case SNDRV_PCM_IOCTL_HW_FREE: + return snd_pcm_hw_free(substream); + case SNDRV_PCM_IOCTL_SW_PARAMS: + return snd_pcm_sw_params_user(substream, arg); + case SNDRV_PCM_IOCTL_STATUS: + return snd_pcm_status_user(substream, arg); + case SNDRV_PCM_IOCTL_CHANNEL_INFO: + return snd_pcm_channel_info_user(substream, arg); + case SNDRV_PCM_IOCTL_PREPARE: + return snd_pcm_prepare(substream); + case SNDRV_PCM_IOCTL_RESET: + return snd_pcm_reset(substream); + case SNDRV_PCM_IOCTL_START: + return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING); + case SNDRV_PCM_IOCTL_LINK: + return snd_pcm_link(substream, (int)(unsigned long) arg); + case SNDRV_PCM_IOCTL_UNLINK: + return snd_pcm_unlink(substream); + case SNDRV_PCM_IOCTL_RESUME: + return snd_pcm_resume(substream); + case SNDRV_PCM_IOCTL_XRUN: + return snd_pcm_xrun(substream); + case SNDRV_PCM_IOCTL_HWSYNC: + return snd_pcm_hwsync(substream); + case SNDRV_PCM_IOCTL_DELAY: + return snd_pcm_delay(substream, arg); + case SNDRV_PCM_IOCTL_SYNC_PTR: + return snd_pcm_sync_ptr(substream, arg); + case SNDRV_PCM_IOCTL_HW_REFINE_OLD: + return snd_pcm_hw_refine_old_user(substream, arg); + case SNDRV_PCM_IOCTL_HW_PARAMS_OLD: + return snd_pcm_hw_params_old_user(substream, arg); + case SNDRV_PCM_IOCTL_DRAIN: + return snd_pcm_drain(substream); + case SNDRV_PCM_IOCTL_DROP: + return snd_pcm_drop(substream); + } + snd_printd("unknown ioctl = 0x%x\n", cmd); + return -ENOTTY; +} + +static int snd_pcm_playback_ioctl1(snd_pcm_substream_t *substream, + unsigned int cmd, void __user *arg) +{ + snd_assert(substream != NULL, return -ENXIO); + snd_assert(substream->stream == SNDRV_PCM_STREAM_PLAYBACK, return -EINVAL); + switch (cmd) { + case SNDRV_PCM_IOCTL_WRITEI_FRAMES: + { + snd_xferi_t xferi; + snd_xferi_t __user *_xferi = arg; + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_sframes_t result; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (put_user(0, &_xferi->result)) + return -EFAULT; + if (copy_from_user(&xferi, _xferi, sizeof(xferi))) + return -EFAULT; + result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames); + __put_user(result, &_xferi->result); + return result < 0 ? result : 0; + } + case SNDRV_PCM_IOCTL_WRITEN_FRAMES: + { + snd_xfern_t xfern; + snd_xfern_t __user *_xfern = arg; + snd_pcm_runtime_t *runtime = substream->runtime; + void __user **bufs; + snd_pcm_sframes_t result; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (runtime->channels > 128) + return -EINVAL; + if (put_user(0, &_xfern->result)) + return -EFAULT; + if (copy_from_user(&xfern, _xfern, sizeof(xfern))) + return -EFAULT; + bufs = kmalloc(sizeof(void *) * runtime->channels, GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + if (copy_from_user(bufs, xfern.bufs, sizeof(void *) * runtime->channels)) { + kfree(bufs); + return -EFAULT; + } + result = snd_pcm_lib_writev(substream, bufs, xfern.frames); + kfree(bufs); + __put_user(result, &_xfern->result); + return result < 0 ? result : 0; + } + case SNDRV_PCM_IOCTL_REWIND: + { + snd_pcm_uframes_t frames; + snd_pcm_uframes_t __user *_frames = arg; + snd_pcm_sframes_t result; + if (get_user(frames, _frames)) + return -EFAULT; + if (put_user(0, _frames)) + return -EFAULT; + result = snd_pcm_playback_rewind(substream, frames); + __put_user(result, _frames); + return result < 0 ? result : 0; + } + case SNDRV_PCM_IOCTL_FORWARD: + { + snd_pcm_uframes_t frames; + snd_pcm_uframes_t __user *_frames = arg; + snd_pcm_sframes_t result; + if (get_user(frames, _frames)) + return -EFAULT; + if (put_user(0, _frames)) + return -EFAULT; + result = snd_pcm_playback_forward(substream, frames); + __put_user(result, _frames); + return result < 0 ? result : 0; + } + case SNDRV_PCM_IOCTL_PAUSE: + { + int res; + snd_pcm_stream_lock_irq(substream); + res = snd_pcm_pause(substream, (int)(unsigned long)arg); + snd_pcm_stream_unlock_irq(substream); + return res; + } + } + return snd_pcm_common_ioctl1(substream, cmd, arg); +} + +static int snd_pcm_capture_ioctl1(snd_pcm_substream_t *substream, + unsigned int cmd, void __user *arg) +{ + snd_assert(substream != NULL, return -ENXIO); + snd_assert(substream->stream == SNDRV_PCM_STREAM_CAPTURE, return -EINVAL); + switch (cmd) { + case SNDRV_PCM_IOCTL_READI_FRAMES: + { + snd_xferi_t xferi; + snd_xferi_t __user *_xferi = arg; + snd_pcm_runtime_t *runtime = substream->runtime; + snd_pcm_sframes_t result; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (put_user(0, &_xferi->result)) + return -EFAULT; + if (copy_from_user(&xferi, _xferi, sizeof(xferi))) + return -EFAULT; + result = snd_pcm_lib_read(substream, xferi.buf, xferi.frames); + __put_user(result, &_xferi->result); + return result < 0 ? result : 0; + } + case SNDRV_PCM_IOCTL_READN_FRAMES: + { + snd_xfern_t xfern; + snd_xfern_t __user *_xfern = arg; + snd_pcm_runtime_t *runtime = substream->runtime; + void *bufs; + snd_pcm_sframes_t result; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (runtime->channels > 128) + return -EINVAL; + if (put_user(0, &_xfern->result)) + return -EFAULT; + if (copy_from_user(&xfern, _xfern, sizeof(xfern))) + return -EFAULT; + bufs = kmalloc(sizeof(void *) * runtime->channels, GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + if (copy_from_user(bufs, xfern.bufs, sizeof(void *) * runtime->channels)) { + kfree(bufs); + return -EFAULT; + } + result = snd_pcm_lib_readv(substream, bufs, xfern.frames); + kfree(bufs); + __put_user(result, &_xfern->result); + return result < 0 ? result : 0; + } + case SNDRV_PCM_IOCTL_REWIND: + { + snd_pcm_uframes_t frames; + snd_pcm_uframes_t __user *_frames = arg; + snd_pcm_sframes_t result; + if (get_user(frames, _frames)) + return -EFAULT; + if (put_user(0, _frames)) + return -EFAULT; + result = snd_pcm_capture_rewind(substream, frames); + __put_user(result, _frames); + return result < 0 ? result : 0; + } + case SNDRV_PCM_IOCTL_FORWARD: + { + snd_pcm_uframes_t frames; + snd_pcm_uframes_t __user *_frames = arg; + snd_pcm_sframes_t result; + if (get_user(frames, _frames)) + return -EFAULT; + if (put_user(0, _frames)) + return -EFAULT; + result = snd_pcm_capture_forward(substream, frames); + __put_user(result, _frames); + return result < 0 ? result : 0; + } + } + return snd_pcm_common_ioctl1(substream, cmd, arg); +} + +static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_pcm_file_t *pcm_file; + + pcm_file = file->private_data; + + if (((cmd >> 8) & 0xff) != 'A') + return -ENOTTY; + + return snd_pcm_playback_ioctl1(pcm_file->substream, cmd, (void __user *)arg); +} + +static long snd_pcm_capture_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_pcm_file_t *pcm_file; + + pcm_file = file->private_data; + + if (((cmd >> 8) & 0xff) != 'A') + return -ENOTTY; + + return snd_pcm_capture_ioctl1(pcm_file->substream, cmd, (void __user *)arg); +} + +int snd_pcm_kernel_playback_ioctl(snd_pcm_substream_t *substream, + unsigned int cmd, void *arg) +{ + mm_segment_t fs; + int result; + + fs = snd_enter_user(); + result = snd_pcm_playback_ioctl1(substream, cmd, (void __user *)arg); + snd_leave_user(fs); + return result; +} + +int snd_pcm_kernel_capture_ioctl(snd_pcm_substream_t *substream, + unsigned int cmd, void *arg) +{ + mm_segment_t fs; + int result; + + fs = snd_enter_user(); + result = snd_pcm_capture_ioctl1(substream, cmd, (void __user *)arg); + snd_leave_user(fs); + return result; +} + +int snd_pcm_kernel_ioctl(snd_pcm_substream_t *substream, + unsigned int cmd, void *arg) +{ + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + return snd_pcm_kernel_playback_ioctl(substream, cmd, arg); + case SNDRV_PCM_STREAM_CAPTURE: + return snd_pcm_kernel_capture_ioctl(substream, cmd, arg); + default: + return -EINVAL; + } +} + +static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count, loff_t * offset) +{ + snd_pcm_file_t *pcm_file; + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + snd_pcm_sframes_t result; + + pcm_file = file->private_data; + substream = pcm_file->substream; + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (!frame_aligned(runtime, count)) + return -EINVAL; + count = bytes_to_frames(runtime, count); + result = snd_pcm_lib_read(substream, buf, count); + if (result > 0) + result = frames_to_bytes(runtime, result); + return result; +} + +static ssize_t snd_pcm_write(struct file *file, const char __user *buf, size_t count, loff_t * offset) +{ + snd_pcm_file_t *pcm_file; + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + snd_pcm_sframes_t result; + + pcm_file = file->private_data; + substream = pcm_file->substream; + snd_assert(substream != NULL, result = -ENXIO; goto end); + runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) { + result = -EBADFD; + goto end; + } + if (!frame_aligned(runtime, count)) { + result = -EINVAL; + goto end; + } + count = bytes_to_frames(runtime, count); + result = snd_pcm_lib_write(substream, buf, count); + if (result > 0) + result = frames_to_bytes(runtime, result); + end: + return result; +} + +static ssize_t snd_pcm_readv(struct file *file, const struct iovec *_vector, + unsigned long count, loff_t * offset) + +{ + snd_pcm_file_t *pcm_file; + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + snd_pcm_sframes_t result; + unsigned long i; + void __user **bufs; + snd_pcm_uframes_t frames; + + pcm_file = file->private_data; + substream = pcm_file->substream; + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (count > 1024 || count != runtime->channels) + return -EINVAL; + if (!frame_aligned(runtime, _vector->iov_len)) + return -EINVAL; + frames = bytes_to_samples(runtime, _vector->iov_len); + bufs = kmalloc(sizeof(void *) * count, GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + for (i = 0; i < count; ++i) + bufs[i] = _vector[i].iov_base; + result = snd_pcm_lib_readv(substream, bufs, frames); + if (result > 0) + result = frames_to_bytes(runtime, result); + kfree(bufs); + return result; +} + +static ssize_t snd_pcm_writev(struct file *file, const struct iovec *_vector, + unsigned long count, loff_t * offset) +{ + snd_pcm_file_t *pcm_file; + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + snd_pcm_sframes_t result; + unsigned long i; + void __user **bufs; + snd_pcm_uframes_t frames; + + pcm_file = file->private_data; + substream = pcm_file->substream; + snd_assert(substream != NULL, result = -ENXIO; goto end); + runtime = substream->runtime; + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) { + result = -EBADFD; + goto end; + } + if (count > 128 || count != runtime->channels || + !frame_aligned(runtime, _vector->iov_len)) { + result = -EINVAL; + goto end; + } + frames = bytes_to_samples(runtime, _vector->iov_len); + bufs = kmalloc(sizeof(void *) * count, GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + for (i = 0; i < count; ++i) + bufs[i] = _vector[i].iov_base; + result = snd_pcm_lib_writev(substream, bufs, frames); + if (result > 0) + result = frames_to_bytes(runtime, result); + kfree(bufs); + end: + return result; +} + +static unsigned int snd_pcm_playback_poll(struct file *file, poll_table * wait) +{ + snd_pcm_file_t *pcm_file; + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + unsigned int mask; + snd_pcm_uframes_t avail; + + pcm_file = file->private_data; + + substream = pcm_file->substream; + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + + poll_wait(file, &runtime->sleep, wait); + + snd_pcm_stream_lock_irq(substream); + avail = snd_pcm_playback_avail(runtime); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + if (avail >= runtime->control->avail_min) { + mask = POLLOUT | POLLWRNORM; + break; + } + /* Fall through */ + case SNDRV_PCM_STATE_DRAINING: + mask = 0; + break; + default: + mask = POLLOUT | POLLWRNORM | POLLERR; + break; + } + snd_pcm_stream_unlock_irq(substream); + return mask; +} + +static unsigned int snd_pcm_capture_poll(struct file *file, poll_table * wait) +{ + snd_pcm_file_t *pcm_file; + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + unsigned int mask; + snd_pcm_uframes_t avail; + + pcm_file = file->private_data; + + substream = pcm_file->substream; + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + + poll_wait(file, &runtime->sleep, wait); + + snd_pcm_stream_lock_irq(substream); + avail = snd_pcm_capture_avail(runtime); + switch (runtime->status->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + if (avail >= runtime->control->avail_min) { + mask = POLLIN | POLLRDNORM; + break; + } + mask = 0; + break; + case SNDRV_PCM_STATE_DRAINING: + if (avail > 0) { + mask = POLLIN | POLLRDNORM; + break; + } + /* Fall through */ + default: + mask = POLLIN | POLLRDNORM | POLLERR; + break; + } + snd_pcm_stream_unlock_irq(substream); + return mask; +} + +/* + * mmap support + */ + +/* + * Only on coherent architectures, we can mmap the status and the control records + * for effcient data transfer. On others, we have to use HWSYNC ioctl... + */ +#if defined(CONFIG_X86) || defined(CONFIG_PPC) || defined(CONFIG_ALPHA) +/* + * mmap status record + */ +static struct page * snd_pcm_mmap_status_nopage(struct vm_area_struct *area, unsigned long address, int *type) +{ + snd_pcm_substream_t *substream = (snd_pcm_substream_t *)area->vm_private_data; + snd_pcm_runtime_t *runtime; + struct page * page; + + if (substream == NULL) + return NOPAGE_OOM; + runtime = substream->runtime; + page = virt_to_page(runtime->status); + if (!PageReserved(page)) + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + return page; +} + +static struct vm_operations_struct snd_pcm_vm_ops_status = +{ + .nopage = snd_pcm_mmap_status_nopage, +}; + +static int snd_pcm_mmap_status(snd_pcm_substream_t *substream, struct file *file, + struct vm_area_struct *area) +{ + snd_pcm_runtime_t *runtime; + long size; + if (!(area->vm_flags & VM_READ)) + return -EINVAL; + runtime = substream->runtime; + snd_assert(runtime != NULL, return -EAGAIN); + size = area->vm_end - area->vm_start; + if (size != PAGE_ALIGN(sizeof(snd_pcm_mmap_status_t))) + return -EINVAL; + area->vm_ops = &snd_pcm_vm_ops_status; + area->vm_private_data = substream; + area->vm_flags |= VM_RESERVED; + return 0; +} + +/* + * mmap control record + */ +static struct page * snd_pcm_mmap_control_nopage(struct vm_area_struct *area, unsigned long address, int *type) +{ + snd_pcm_substream_t *substream = (snd_pcm_substream_t *)area->vm_private_data; + snd_pcm_runtime_t *runtime; + struct page * page; + + if (substream == NULL) + return NOPAGE_OOM; + runtime = substream->runtime; + page = virt_to_page(runtime->control); + if (!PageReserved(page)) + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + return page; +} + +static struct vm_operations_struct snd_pcm_vm_ops_control = +{ + .nopage = snd_pcm_mmap_control_nopage, +}; + +static int snd_pcm_mmap_control(snd_pcm_substream_t *substream, struct file *file, + struct vm_area_struct *area) +{ + snd_pcm_runtime_t *runtime; + long size; + if (!(area->vm_flags & VM_READ)) + return -EINVAL; + runtime = substream->runtime; + snd_assert(runtime != NULL, return -EAGAIN); + size = area->vm_end - area->vm_start; + if (size != PAGE_ALIGN(sizeof(snd_pcm_mmap_control_t))) + return -EINVAL; + area->vm_ops = &snd_pcm_vm_ops_control; + area->vm_private_data = substream; + area->vm_flags |= VM_RESERVED; + return 0; +} +#else /* ! coherent mmap */ +/* + * don't support mmap for status and control records. + */ +static int snd_pcm_mmap_status(snd_pcm_substream_t *substream, struct file *file, + struct vm_area_struct *area) +{ + return -ENXIO; +} +static int snd_pcm_mmap_control(snd_pcm_substream_t *substream, struct file *file, + struct vm_area_struct *area) +{ + return -ENXIO; +} +#endif /* coherent mmap */ + +/* + * nopage callback for mmapping a RAM page + */ +static struct page *snd_pcm_mmap_data_nopage(struct vm_area_struct *area, unsigned long address, int *type) +{ + snd_pcm_substream_t *substream = (snd_pcm_substream_t *)area->vm_private_data; + snd_pcm_runtime_t *runtime; + unsigned long offset; + struct page * page; + void *vaddr; + size_t dma_bytes; + + if (substream == NULL) + return NOPAGE_OOM; + runtime = substream->runtime; + offset = area->vm_pgoff << PAGE_SHIFT; + offset += address - area->vm_start; + snd_assert((offset % PAGE_SIZE) == 0, return NOPAGE_OOM); + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if (offset > dma_bytes - PAGE_SIZE) + return NOPAGE_SIGBUS; + if (substream->ops->page) { + page = substream->ops->page(substream, offset); + if (! page) + return NOPAGE_OOM; + } else { + vaddr = runtime->dma_area + offset; + page = virt_to_page(vaddr); + } + if (!PageReserved(page)) + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + return page; +} + +static struct vm_operations_struct snd_pcm_vm_ops_data = +{ + .open = snd_pcm_mmap_data_open, + .close = snd_pcm_mmap_data_close, + .nopage = snd_pcm_mmap_data_nopage, +}; + +/* + * mmap the DMA buffer on RAM + */ +static int snd_pcm_default_mmap(snd_pcm_substream_t *substream, struct vm_area_struct *area) +{ + area->vm_ops = &snd_pcm_vm_ops_data; + area->vm_private_data = substream; + area->vm_flags |= VM_RESERVED; + atomic_inc(&substream->runtime->mmap_count); + return 0; +} + +/* + * mmap the DMA buffer on I/O memory area + */ +#if SNDRV_PCM_INFO_MMAP_IOMEM +static struct vm_operations_struct snd_pcm_vm_ops_data_mmio = +{ + .open = snd_pcm_mmap_data_open, + .close = snd_pcm_mmap_data_close, +}; + +int snd_pcm_lib_mmap_iomem(snd_pcm_substream_t *substream, struct vm_area_struct *area) +{ + long size; + unsigned long offset; + +#ifdef pgprot_noncached + area->vm_page_prot = pgprot_noncached(area->vm_page_prot); +#endif + area->vm_ops = &snd_pcm_vm_ops_data_mmio; + area->vm_private_data = substream; + area->vm_flags |= VM_IO; + size = area->vm_end - area->vm_start; + offset = area->vm_pgoff << PAGE_SHIFT; + if (io_remap_pfn_range(area, area->vm_start, + (substream->runtime->dma_addr + offset) >> PAGE_SHIFT, + size, area->vm_page_prot)) + return -EAGAIN; + atomic_inc(&substream->runtime->mmap_count); + return 0; +} +#endif /* SNDRV_PCM_INFO_MMAP */ + +/* + * mmap DMA buffer + */ +int snd_pcm_mmap_data(snd_pcm_substream_t *substream, struct file *file, + struct vm_area_struct *area) +{ + snd_pcm_runtime_t *runtime; + long size; + unsigned long offset; + size_t dma_bytes; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (!(area->vm_flags & (VM_WRITE|VM_READ))) + return -EINVAL; + } else { + if (!(area->vm_flags & VM_READ)) + return -EINVAL; + } + runtime = substream->runtime; + snd_assert(runtime != NULL, return -EAGAIN); + if (runtime->status->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) + return -ENXIO; + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) + return -EINVAL; + size = area->vm_end - area->vm_start; + offset = area->vm_pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if ((size_t)size > dma_bytes) + return -EINVAL; + if (offset > dma_bytes - size) + return -EINVAL; + + if (substream->ops->mmap) + return substream->ops->mmap(substream, area); + else + return snd_pcm_default_mmap(substream, area); +} + +static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area) +{ + snd_pcm_file_t * pcm_file; + snd_pcm_substream_t *substream; + unsigned long offset; + + pcm_file = file->private_data; + substream = pcm_file->substream; + snd_assert(substream != NULL, return -ENXIO); + + offset = area->vm_pgoff << PAGE_SHIFT; + switch (offset) { + case SNDRV_PCM_MMAP_OFFSET_STATUS: + if (substream->no_mmap_ctrl) + return -ENXIO; + return snd_pcm_mmap_status(substream, file, area); + case SNDRV_PCM_MMAP_OFFSET_CONTROL: + if (substream->no_mmap_ctrl) + return -ENXIO; + return snd_pcm_mmap_control(substream, file, area); + default: + return snd_pcm_mmap_data(substream, file, area); + } + return 0; +} + +static int snd_pcm_fasync(int fd, struct file * file, int on) +{ + snd_pcm_file_t * pcm_file; + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + int err; + + pcm_file = file->private_data; + substream = pcm_file->substream; + snd_assert(substream != NULL, return -ENXIO); + runtime = substream->runtime; + + err = fasync_helper(fd, file, on, &runtime->fasync); + if (err < 0) + return err; + return 0; +} + +/* + * ioctl32 compat + */ +#ifdef CONFIG_COMPAT +#include "pcm_compat.c" +#else +#define snd_pcm_ioctl_compat NULL +#endif + +/* + * To be removed helpers to keep binary compatibility + */ + +#define __OLD_TO_NEW_MASK(x) ((x&7)|((x&0x07fffff8)<<5)) +#define __NEW_TO_OLD_MASK(x) ((x&7)|((x&0xffffff00)>>5)) + +static void snd_pcm_hw_convert_from_old_params(snd_pcm_hw_params_t *params, struct sndrv_pcm_hw_params_old *oparams) +{ + unsigned int i; + + memset(params, 0, sizeof(*params)); + params->flags = oparams->flags; + for (i = 0; i < ARRAY_SIZE(oparams->masks); i++) + params->masks[i].bits[0] = oparams->masks[i]; + memcpy(params->intervals, oparams->intervals, sizeof(oparams->intervals)); + params->rmask = __OLD_TO_NEW_MASK(oparams->rmask); + params->cmask = __OLD_TO_NEW_MASK(oparams->cmask); + params->info = oparams->info; + params->msbits = oparams->msbits; + params->rate_num = oparams->rate_num; + params->rate_den = oparams->rate_den; + params->fifo_size = oparams->fifo_size; +} + +static void snd_pcm_hw_convert_to_old_params(struct sndrv_pcm_hw_params_old *oparams, snd_pcm_hw_params_t *params) +{ + unsigned int i; + + memset(oparams, 0, sizeof(*oparams)); + oparams->flags = params->flags; + for (i = 0; i < ARRAY_SIZE(oparams->masks); i++) + oparams->masks[i] = params->masks[i].bits[0]; + memcpy(oparams->intervals, params->intervals, sizeof(oparams->intervals)); + oparams->rmask = __NEW_TO_OLD_MASK(params->rmask); + oparams->cmask = __NEW_TO_OLD_MASK(params->cmask); + oparams->info = params->info; + oparams->msbits = params->msbits; + oparams->rate_num = params->rate_num; + oparams->rate_den = params->rate_den; + oparams->fifo_size = params->fifo_size; +} + +static int snd_pcm_hw_refine_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams) +{ + snd_pcm_hw_params_t *params; + struct sndrv_pcm_hw_params_old *oparams = NULL; + int err; + + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) { + err = -ENOMEM; + goto out; + } + oparams = kmalloc(sizeof(*oparams), GFP_KERNEL); + if (!oparams) { + err = -ENOMEM; + goto out; + } + + if (copy_from_user(oparams, _oparams, sizeof(*oparams))) { + err = -EFAULT; + goto out; + } + snd_pcm_hw_convert_from_old_params(params, oparams); + err = snd_pcm_hw_refine(substream, params); + snd_pcm_hw_convert_to_old_params(oparams, params); + if (copy_to_user(_oparams, oparams, sizeof(*oparams))) { + if (!err) + err = -EFAULT; + } +out: + kfree(params); + kfree(oparams); + return err; +} + +static int snd_pcm_hw_params_old_user(snd_pcm_substream_t * substream, struct sndrv_pcm_hw_params_old __user * _oparams) +{ + snd_pcm_hw_params_t *params; + struct sndrv_pcm_hw_params_old *oparams = NULL; + int err; + + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) { + err = -ENOMEM; + goto out; + } + oparams = kmalloc(sizeof(*oparams), GFP_KERNEL); + if (!oparams) { + err = -ENOMEM; + goto out; + } + if (copy_from_user(oparams, _oparams, sizeof(*oparams))) { + err = -EFAULT; + goto out; + } + snd_pcm_hw_convert_from_old_params(params, oparams); + err = snd_pcm_hw_params(substream, params); + snd_pcm_hw_convert_to_old_params(oparams, params); + if (copy_to_user(_oparams, oparams, sizeof(*oparams))) { + if (!err) + err = -EFAULT; + } +out: + kfree(params); + kfree(oparams); + return err; +} + +/* + * Register section + */ + +static struct file_operations snd_pcm_f_ops_playback = { + .owner = THIS_MODULE, + .write = snd_pcm_write, + .writev = snd_pcm_writev, + .open = snd_pcm_open, + .release = snd_pcm_release, + .poll = snd_pcm_playback_poll, + .unlocked_ioctl = snd_pcm_playback_ioctl, + .compat_ioctl = snd_pcm_ioctl_compat, + .mmap = snd_pcm_mmap, + .fasync = snd_pcm_fasync, +}; + +static struct file_operations snd_pcm_f_ops_capture = { + .owner = THIS_MODULE, + .read = snd_pcm_read, + .readv = snd_pcm_readv, + .open = snd_pcm_open, + .release = snd_pcm_release, + .poll = snd_pcm_capture_poll, + .unlocked_ioctl = snd_pcm_capture_ioctl, + .compat_ioctl = snd_pcm_ioctl_compat, + .mmap = snd_pcm_mmap, + .fasync = snd_pcm_fasync, +}; + +snd_minor_t snd_pcm_reg[2] = +{ + { + .comment = "digital audio playback", + .f_ops = &snd_pcm_f_ops_playback, + }, + { + .comment = "digital audio capture", + .f_ops = &snd_pcm_f_ops_capture, + } +}; diff --git a/sound/core/pcm_timer.c b/sound/core/pcm_timer.c new file mode 100644 index 000000000000..884eaea31fec --- /dev/null +++ b/sound/core/pcm_timer.c @@ -0,0 +1,161 @@ +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/timer.h> + +/* + * Timer functions + */ + +/* Greatest common divisor */ +static unsigned long gcd(unsigned long a, unsigned long b) +{ + unsigned long r; + if (a < b) { + r = a; + a = b; + b = r; + } + while ((r = a % b) != 0) { + a = b; + b = r; + } + return b; +} + +void snd_pcm_timer_resolution_change(snd_pcm_substream_t *substream) +{ + unsigned long rate, mult, fsize, l, post; + snd_pcm_runtime_t *runtime = substream->runtime; + + mult = 1000000000; + rate = runtime->rate; + snd_assert(rate != 0, return); + l = gcd(mult, rate); + mult /= l; + rate /= l; + fsize = runtime->period_size; + snd_assert(fsize != 0, return); + l = gcd(rate, fsize); + rate /= l; + fsize /= l; + post = 1; + while ((mult * fsize) / fsize != mult) { + mult /= 2; + post *= 2; + } + if (rate == 0) { + snd_printk(KERN_ERR "pcm timer resolution out of range (rate = %u, period_size = %lu)\n", runtime->rate, runtime->period_size); + runtime->timer_resolution = -1; + return; + } + runtime->timer_resolution = (mult * fsize / rate) * post; +} + +static unsigned long snd_pcm_timer_resolution(snd_timer_t * timer) +{ + snd_pcm_substream_t * substream; + + substream = timer->private_data; + return substream->runtime ? substream->runtime->timer_resolution : 0; +} + +static int snd_pcm_timer_start(snd_timer_t * timer) +{ + unsigned long flags; + snd_pcm_substream_t * substream; + + substream = snd_timer_chip(timer); + spin_lock_irqsave(&substream->timer_lock, flags); + substream->timer_running = 1; + spin_unlock_irqrestore(&substream->timer_lock, flags); + return 0; +} + +static int snd_pcm_timer_stop(snd_timer_t * timer) +{ + unsigned long flags; + snd_pcm_substream_t * substream; + + substream = snd_timer_chip(timer); + spin_lock_irqsave(&substream->timer_lock, flags); + substream->timer_running = 0; + spin_unlock_irqrestore(&substream->timer_lock, flags); + return 0; +} + +static struct _snd_timer_hardware snd_pcm_timer = +{ + .flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE, + .resolution = 0, + .ticks = 1, + .c_resolution = snd_pcm_timer_resolution, + .start = snd_pcm_timer_start, + .stop = snd_pcm_timer_stop, +}; + +/* + * Init functions + */ + +static void snd_pcm_timer_free(snd_timer_t *timer) +{ + snd_pcm_substream_t *substream = timer->private_data; + substream->timer = NULL; +} + +void snd_pcm_timer_init(snd_pcm_substream_t *substream) +{ + snd_timer_id_t tid; + snd_timer_t *timer; + + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.dev_class = SNDRV_TIMER_CLASS_PCM; + tid.card = substream->pcm->card->number; + tid.device = substream->pcm->device; + tid.subdevice = (substream->number << 1) | (substream->stream & 1); + if (snd_timer_new(substream->pcm->card, "PCM", &tid, &timer) < 0) + return; + sprintf(timer->name, "PCM %s %i-%i-%i", + substream->stream == SNDRV_PCM_STREAM_CAPTURE ? + "capture" : "playback", + tid.card, tid.device, tid.subdevice); + timer->hw = snd_pcm_timer; + if (snd_device_register(timer->card, timer) < 0) { + snd_device_free(timer->card, timer); + return; + } + timer->private_data = substream; + timer->private_free = snd_pcm_timer_free; + substream->timer = timer; +} + +void snd_pcm_timer_done(snd_pcm_substream_t *substream) +{ + if (substream->timer) { + snd_device_free(substream->pcm->card, substream->timer); + substream->timer = NULL; + } +} diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c new file mode 100644 index 000000000000..edba4118271c --- /dev/null +++ b/sound/core/rawmidi.c @@ -0,0 +1,1680 @@ +/* + * Abstract layer for MIDI v1.0 stream + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <sound/core.h> +#include <linux/major.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <sound/rawmidi.h> +#include <sound/info.h> +#include <sound/control.h> +#include <sound/minors.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Midlevel RawMidi code for ALSA."); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_SND_OSSEMUL +static int midi_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 0}; +static int amidi_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +module_param_array(midi_map, int, NULL, 0444); +MODULE_PARM_DESC(midi_map, "Raw MIDI device number assigned to 1st OSS device."); +module_param_array(amidi_map, int, NULL, 0444); +MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device."); +#endif /* CONFIG_SND_OSSEMUL */ + +static int snd_rawmidi_free(snd_rawmidi_t *rawmidi); +static int snd_rawmidi_dev_free(snd_device_t *device); +static int snd_rawmidi_dev_register(snd_device_t *device); +static int snd_rawmidi_dev_disconnect(snd_device_t *device); +static int snd_rawmidi_dev_unregister(snd_device_t *device); + +static snd_rawmidi_t *snd_rawmidi_devices[SNDRV_CARDS * SNDRV_RAWMIDI_DEVICES]; + +static DECLARE_MUTEX(register_mutex); + +static inline unsigned short snd_rawmidi_file_flags(struct file *file) +{ + switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) { + case FMODE_WRITE: + return SNDRV_RAWMIDI_LFLG_OUTPUT; + case FMODE_READ: + return SNDRV_RAWMIDI_LFLG_INPUT; + default: + return SNDRV_RAWMIDI_LFLG_OPEN; + } +} + +static inline int snd_rawmidi_ready(snd_rawmidi_substream_t * substream) +{ + snd_rawmidi_runtime_t *runtime = substream->runtime; + return runtime->avail >= runtime->avail_min; +} + +static inline int snd_rawmidi_ready_append(snd_rawmidi_substream_t * substream, size_t count) +{ + snd_rawmidi_runtime_t *runtime = substream->runtime; + return runtime->avail >= runtime->avail_min && + (!substream->append || runtime->avail >= count); +} + +static void snd_rawmidi_input_event_tasklet(unsigned long data) +{ + snd_rawmidi_substream_t *substream = (snd_rawmidi_substream_t *)data; + substream->runtime->event(substream); +} + +static void snd_rawmidi_output_trigger_tasklet(unsigned long data) +{ + snd_rawmidi_substream_t *substream = (snd_rawmidi_substream_t *)data; + substream->ops->trigger(substream, 1); +} + +static int snd_rawmidi_runtime_create(snd_rawmidi_substream_t * substream) +{ + snd_rawmidi_runtime_t *runtime; + + if ((runtime = kcalloc(1, sizeof(*runtime), GFP_KERNEL)) == NULL) + return -ENOMEM; + spin_lock_init(&runtime->lock); + init_waitqueue_head(&runtime->sleep); + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + tasklet_init(&runtime->tasklet, + snd_rawmidi_input_event_tasklet, + (unsigned long)substream); + else + tasklet_init(&runtime->tasklet, + snd_rawmidi_output_trigger_tasklet, + (unsigned long)substream); + runtime->event = NULL; + runtime->buffer_size = PAGE_SIZE; + runtime->avail_min = 1; + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + runtime->avail = 0; + else + runtime->avail = runtime->buffer_size; + if ((runtime->buffer = kmalloc(runtime->buffer_size, GFP_KERNEL)) == NULL) { + kfree(runtime); + return -ENOMEM; + } + runtime->appl_ptr = runtime->hw_ptr = 0; + substream->runtime = runtime; + return 0; +} + +static int snd_rawmidi_runtime_free(snd_rawmidi_substream_t * substream) +{ + snd_rawmidi_runtime_t *runtime = substream->runtime; + + kfree(runtime->buffer); + kfree(runtime); + substream->runtime = NULL; + return 0; +} + +static inline void snd_rawmidi_output_trigger(snd_rawmidi_substream_t * substream, int up) +{ + if (up) { + tasklet_hi_schedule(&substream->runtime->tasklet); + } else { + tasklet_kill(&substream->runtime->tasklet); + substream->ops->trigger(substream, 0); + } +} + +static void snd_rawmidi_input_trigger(snd_rawmidi_substream_t * substream, int up) +{ + substream->ops->trigger(substream, up); + if (!up && substream->runtime->event) + tasklet_kill(&substream->runtime->tasklet); +} + +int snd_rawmidi_drop_output(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + snd_rawmidi_runtime_t *runtime = substream->runtime; + + snd_rawmidi_output_trigger(substream, 0); + runtime->drain = 0; + spin_lock_irqsave(&runtime->lock, flags); + runtime->appl_ptr = runtime->hw_ptr = 0; + runtime->avail = runtime->buffer_size; + spin_unlock_irqrestore(&runtime->lock, flags); + return 0; +} + +int snd_rawmidi_drain_output(snd_rawmidi_substream_t * substream) +{ + int err; + long timeout; + snd_rawmidi_runtime_t *runtime = substream->runtime; + + err = 0; + runtime->drain = 1; + timeout = wait_event_interruptible_timeout(runtime->sleep, + (runtime->avail >= runtime->buffer_size), + 10*HZ); + if (signal_pending(current)) + err = -ERESTARTSYS; + if (runtime->avail < runtime->buffer_size && !timeout) { + snd_printk(KERN_WARNING "rawmidi drain error (avail = %li, buffer_size = %li)\n", (long)runtime->avail, (long)runtime->buffer_size); + err = -EIO; + } + runtime->drain = 0; + if (err != -ERESTARTSYS) { + /* we need wait a while to make sure that Tx FIFOs are empty */ + if (substream->ops->drain) + substream->ops->drain(substream); + else + msleep(50); + snd_rawmidi_drop_output(substream); + } + return err; +} + +int snd_rawmidi_drain_input(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + snd_rawmidi_runtime_t *runtime = substream->runtime; + + snd_rawmidi_input_trigger(substream, 0); + runtime->drain = 0; + spin_lock_irqsave(&runtime->lock, flags); + runtime->appl_ptr = runtime->hw_ptr = 0; + runtime->avail = 0; + spin_unlock_irqrestore(&runtime->lock, flags); + return 0; +} + +int snd_rawmidi_kernel_open(int cardnum, int device, int subdevice, + int mode, snd_rawmidi_file_t * rfile) +{ + snd_rawmidi_t *rmidi; + struct list_head *list1, *list2; + snd_rawmidi_substream_t *sinput = NULL, *soutput = NULL; + snd_rawmidi_runtime_t *input = NULL, *output = NULL; + int err; + + if (rfile) + rfile->input = rfile->output = NULL; + rmidi = snd_rawmidi_devices[(cardnum * SNDRV_RAWMIDI_DEVICES) + device]; + if (rmidi == NULL) { + err = -ENODEV; + goto __error1; + } + if (!try_module_get(rmidi->card->module)) { + err = -EFAULT; + goto __error1; + } + if (!(mode & SNDRV_RAWMIDI_LFLG_NOOPENLOCK)) + down(&rmidi->open_mutex); + if (mode & SNDRV_RAWMIDI_LFLG_INPUT) { + if (!(rmidi->info_flags & SNDRV_RAWMIDI_INFO_INPUT)) { + err = -ENXIO; + goto __error; + } + if (subdevice >= 0 && (unsigned int)subdevice >= rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_count) { + err = -ENODEV; + goto __error; + } + if (rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_opened >= + rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_count) { + err = -EAGAIN; + goto __error; + } + } + if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) { + if (!(rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT)) { + err = -ENXIO; + goto __error; + } + if (subdevice >= 0 && (unsigned int)subdevice >= rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_count) { + err = -ENODEV; + goto __error; + } + if (rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_opened >= + rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_count) { + err = -EAGAIN; + goto __error; + } + } + list1 = rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams.next; + while (1) { + if (list1 == &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) { + sinput = NULL; + if (mode & SNDRV_RAWMIDI_LFLG_INPUT) { + err = -EAGAIN; + goto __error; + } + break; + } + sinput = list_entry(list1, snd_rawmidi_substream_t, list); + if ((mode & SNDRV_RAWMIDI_LFLG_INPUT) && sinput->opened) + goto __nexti; + if (subdevice < 0 || (subdevice >= 0 && subdevice == sinput->number)) + break; + __nexti: + list1 = list1->next; + } + list2 = rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams.next; + while (1) { + if (list2 == &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) { + soutput = NULL; + if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) { + err = -EAGAIN; + goto __error; + } + break; + } + soutput = list_entry(list2, snd_rawmidi_substream_t, list); + if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) { + if (mode & SNDRV_RAWMIDI_LFLG_APPEND) { + if (soutput->opened && !soutput->append) + goto __nexto; + } else { + if (soutput->opened) + goto __nexto; + } + } + if (subdevice < 0 || (subdevice >= 0 && subdevice == soutput->number)) + break; + __nexto: + list2 = list2->next; + } + if (mode & SNDRV_RAWMIDI_LFLG_INPUT) { + if ((err = snd_rawmidi_runtime_create(sinput)) < 0) + goto __error; + input = sinput->runtime; + if ((err = sinput->ops->open(sinput)) < 0) + goto __error; + sinput->opened = 1; + rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_opened++; + } else { + sinput = NULL; + } + if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) { + if (soutput->opened) + goto __skip_output; + if ((err = snd_rawmidi_runtime_create(soutput)) < 0) { + if (mode & SNDRV_RAWMIDI_LFLG_INPUT) + sinput->ops->close(sinput); + goto __error; + } + output = soutput->runtime; + if ((err = soutput->ops->open(soutput)) < 0) { + if (mode & SNDRV_RAWMIDI_LFLG_INPUT) + sinput->ops->close(sinput); + goto __error; + } + __skip_output: + soutput->opened = 1; + if (mode & SNDRV_RAWMIDI_LFLG_APPEND) + soutput->append = 1; + if (soutput->use_count++ == 0) + soutput->active_sensing = 1; + rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_opened++; + } else { + soutput = NULL; + } + if (!(mode & SNDRV_RAWMIDI_LFLG_NOOPENLOCK)) + up(&rmidi->open_mutex); + if (rfile) { + rfile->rmidi = rmidi; + rfile->input = sinput; + rfile->output = soutput; + } + return 0; + + __error: + if (input != NULL) + snd_rawmidi_runtime_free(sinput); + if (output != NULL) + snd_rawmidi_runtime_free(soutput); + module_put(rmidi->card->module); + if (!(mode & SNDRV_RAWMIDI_LFLG_NOOPENLOCK)) + up(&rmidi->open_mutex); + __error1: + return err; +} + +static int snd_rawmidi_open(struct inode *inode, struct file *file) +{ + int maj = imajor(inode); + int cardnum; + snd_card_t *card; + int device, subdevice; + unsigned short fflags; + int err; + snd_rawmidi_t *rmidi; + snd_rawmidi_file_t *rawmidi_file; + wait_queue_t wait; + struct list_head *list; + snd_ctl_file_t *kctl; + + switch (maj) { + case CONFIG_SND_MAJOR: + cardnum = SNDRV_MINOR_CARD(iminor(inode)); + cardnum %= SNDRV_CARDS; + device = SNDRV_MINOR_DEVICE(iminor(inode)) - SNDRV_MINOR_RAWMIDI; + device %= SNDRV_MINOR_RAWMIDIS; + break; +#ifdef CONFIG_SND_OSSEMUL + case SOUND_MAJOR: + cardnum = SNDRV_MINOR_OSS_CARD(iminor(inode)); + cardnum %= SNDRV_CARDS; + device = SNDRV_MINOR_OSS_DEVICE(iminor(inode)) == SNDRV_MINOR_OSS_MIDI ? + midi_map[cardnum] : amidi_map[cardnum]; + break; +#endif + default: + return -ENXIO; + } + + rmidi = snd_rawmidi_devices[(cardnum * SNDRV_RAWMIDI_DEVICES) + device]; + if (rmidi == NULL) + return -ENODEV; +#ifdef CONFIG_SND_OSSEMUL + if (maj == SOUND_MAJOR && !rmidi->ossreg) + return -ENXIO; +#endif + if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) + return -EINVAL; /* invalid combination */ + card = rmidi->card; + err = snd_card_file_add(card, file); + if (err < 0) + return -ENODEV; + fflags = snd_rawmidi_file_flags(file); + if ((file->f_flags & O_APPEND) || maj != CONFIG_SND_MAJOR) /* OSS emul? */ + fflags |= SNDRV_RAWMIDI_LFLG_APPEND; + fflags |= SNDRV_RAWMIDI_LFLG_NOOPENLOCK; + rawmidi_file = kmalloc(sizeof(*rawmidi_file), GFP_KERNEL); + if (rawmidi_file == NULL) { + snd_card_file_remove(card, file); + return -ENOMEM; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&rmidi->open_wait, &wait); + down(&rmidi->open_mutex); + while (1) { + subdevice = -1; + down_read(&card->controls_rwsem); + list_for_each(list, &card->ctl_files) { + kctl = snd_ctl_file(list); + if (kctl->pid == current->pid) { + subdevice = kctl->prefer_rawmidi_subdevice; + break; + } + } + up_read(&card->controls_rwsem); + err = snd_rawmidi_kernel_open(cardnum, device, subdevice, fflags, rawmidi_file); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (file->f_flags & O_NONBLOCK) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + up(&rmidi->open_mutex); + schedule(); + down(&rmidi->open_mutex); + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } +#ifdef CONFIG_SND_OSSEMUL + if (rawmidi_file->input && rawmidi_file->input->runtime) + rawmidi_file->input->runtime->oss = (maj == SOUND_MAJOR); + if (rawmidi_file->output && rawmidi_file->output->runtime) + rawmidi_file->output->runtime->oss = (maj == SOUND_MAJOR); +#endif + remove_wait_queue(&rmidi->open_wait, &wait); + if (err >= 0) { + file->private_data = rawmidi_file; + } else { + snd_card_file_remove(card, file); + kfree(rawmidi_file); + } + up(&rmidi->open_mutex); + return err; +} + +int snd_rawmidi_kernel_release(snd_rawmidi_file_t * rfile) +{ + snd_rawmidi_t *rmidi; + snd_rawmidi_substream_t *substream; + snd_rawmidi_runtime_t *runtime; + + snd_assert(rfile != NULL, return -ENXIO); + snd_assert(rfile->input != NULL || rfile->output != NULL, return -ENXIO); + rmidi = rfile->rmidi; + down(&rmidi->open_mutex); + if (rfile->input != NULL) { + substream = rfile->input; + rfile->input = NULL; + runtime = substream->runtime; + snd_rawmidi_input_trigger(substream, 0); + substream->ops->close(substream); + if (runtime->private_free != NULL) + runtime->private_free(substream); + snd_rawmidi_runtime_free(substream); + substream->opened = 0; + rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substream_opened--; + } + if (rfile->output != NULL) { + substream = rfile->output; + rfile->output = NULL; + if (--substream->use_count == 0) { + runtime = substream->runtime; + if (substream->active_sensing) { + unsigned char buf = 0xfe; + /* sending single active sensing message to shut the device up */ + snd_rawmidi_kernel_write(substream, &buf, 1); + } + if (snd_rawmidi_drain_output(substream) == -ERESTARTSYS) + snd_rawmidi_output_trigger(substream, 0); + substream->ops->close(substream); + if (runtime->private_free != NULL) + runtime->private_free(substream); + snd_rawmidi_runtime_free(substream); + substream->opened = 0; + substream->append = 0; + } + rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substream_opened--; + } + up(&rmidi->open_mutex); + module_put(rmidi->card->module); + return 0; +} + +static int snd_rawmidi_release(struct inode *inode, struct file *file) +{ + snd_rawmidi_file_t *rfile; + snd_rawmidi_t *rmidi; + int err; + + rfile = file->private_data; + err = snd_rawmidi_kernel_release(rfile); + rmidi = rfile->rmidi; + wake_up(&rmidi->open_wait); + kfree(rfile); + snd_card_file_remove(rmidi->card, file); + return err; +} + +int snd_rawmidi_info(snd_rawmidi_substream_t *substream, snd_rawmidi_info_t *info) +{ + snd_rawmidi_t *rmidi; + + if (substream == NULL) + return -ENODEV; + rmidi = substream->rmidi; + memset(info, 0, sizeof(*info)); + info->card = rmidi->card->number; + info->device = rmidi->device; + info->subdevice = substream->number; + info->stream = substream->stream; + info->flags = rmidi->info_flags; + strcpy(info->id, rmidi->id); + strcpy(info->name, rmidi->name); + strcpy(info->subname, substream->name); + info->subdevices_count = substream->pstr->substream_count; + info->subdevices_avail = (substream->pstr->substream_count - + substream->pstr->substream_opened); + return 0; +} + +static int snd_rawmidi_info_user(snd_rawmidi_substream_t *substream, snd_rawmidi_info_t __user * _info) +{ + snd_rawmidi_info_t info; + int err; + if ((err = snd_rawmidi_info(substream, &info)) < 0) + return err; + if (copy_to_user(_info, &info, sizeof(snd_rawmidi_info_t))) + return -EFAULT; + return 0; +} + +int snd_rawmidi_info_select(snd_card_t *card, snd_rawmidi_info_t *info) +{ + snd_rawmidi_t *rmidi; + snd_rawmidi_str_t *pstr; + snd_rawmidi_substream_t *substream; + struct list_head *list; + if (info->device >= SNDRV_RAWMIDI_DEVICES) + return -ENXIO; + rmidi = snd_rawmidi_devices[card->number * SNDRV_RAWMIDI_DEVICES + info->device]; + if (info->stream < 0 || info->stream > 1) + return -EINVAL; + pstr = &rmidi->streams[info->stream]; + if (pstr->substream_count == 0) + return -ENOENT; + if (info->subdevice >= pstr->substream_count) + return -ENXIO; + list_for_each(list, &pstr->substreams) { + substream = list_entry(list, snd_rawmidi_substream_t, list); + if ((unsigned int)substream->number == info->subdevice) + return snd_rawmidi_info(substream, info); + } + return -ENXIO; +} + +static int snd_rawmidi_info_select_user(snd_card_t *card, + snd_rawmidi_info_t __user *_info) +{ + int err; + snd_rawmidi_info_t info; + if (get_user(info.device, &_info->device)) + return -EFAULT; + if (get_user(info.stream, &_info->stream)) + return -EFAULT; + if (get_user(info.subdevice, &_info->subdevice)) + return -EFAULT; + if ((err = snd_rawmidi_info_select(card, &info)) < 0) + return err; + if (copy_to_user(_info, &info, sizeof(snd_rawmidi_info_t))) + return -EFAULT; + return 0; +} + +int snd_rawmidi_output_params(snd_rawmidi_substream_t * substream, + snd_rawmidi_params_t * params) +{ + char *newbuf; + snd_rawmidi_runtime_t *runtime = substream->runtime; + + if (substream->append && substream->use_count > 1) + return -EBUSY; + snd_rawmidi_drain_output(substream); + if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) { + return -EINVAL; + } + if (params->avail_min < 1 || params->avail_min > params->buffer_size) { + return -EINVAL; + } + if (params->buffer_size != runtime->buffer_size) { + if ((newbuf = (char *) kmalloc(params->buffer_size, GFP_KERNEL)) == NULL) + return -ENOMEM; + kfree(runtime->buffer); + runtime->buffer = newbuf; + runtime->buffer_size = params->buffer_size; + } + runtime->avail_min = params->avail_min; + substream->active_sensing = !params->no_active_sensing; + return 0; +} + +int snd_rawmidi_input_params(snd_rawmidi_substream_t * substream, + snd_rawmidi_params_t * params) +{ + char *newbuf; + snd_rawmidi_runtime_t *runtime = substream->runtime; + + snd_rawmidi_drain_input(substream); + if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) { + return -EINVAL; + } + if (params->avail_min < 1 || params->avail_min > params->buffer_size) { + return -EINVAL; + } + if (params->buffer_size != runtime->buffer_size) { + if ((newbuf = (char *) kmalloc(params->buffer_size, GFP_KERNEL)) == NULL) + return -ENOMEM; + kfree(runtime->buffer); + runtime->buffer = newbuf; + runtime->buffer_size = params->buffer_size; + } + runtime->avail_min = params->avail_min; + return 0; +} + +static int snd_rawmidi_output_status(snd_rawmidi_substream_t * substream, + snd_rawmidi_status_t * status) +{ + snd_rawmidi_runtime_t *runtime = substream->runtime; + + memset(status, 0, sizeof(*status)); + status->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + spin_lock_irq(&runtime->lock); + status->avail = runtime->avail; + spin_unlock_irq(&runtime->lock); + return 0; +} + +static int snd_rawmidi_input_status(snd_rawmidi_substream_t * substream, + snd_rawmidi_status_t * status) +{ + snd_rawmidi_runtime_t *runtime = substream->runtime; + + memset(status, 0, sizeof(*status)); + status->stream = SNDRV_RAWMIDI_STREAM_INPUT; + spin_lock_irq(&runtime->lock); + status->avail = runtime->avail; + status->xruns = runtime->xruns; + runtime->xruns = 0; + spin_unlock_irq(&runtime->lock); + return 0; +} + +static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_rawmidi_file_t *rfile; + void __user *argp = (void __user *)arg; + + rfile = file->private_data; + if (((cmd >> 8) & 0xff) != 'W') + return -ENOTTY; + switch (cmd) { + case SNDRV_RAWMIDI_IOCTL_PVERSION: + return put_user(SNDRV_RAWMIDI_VERSION, (int __user *)argp) ? -EFAULT : 0; + case SNDRV_RAWMIDI_IOCTL_INFO: + { + snd_rawmidi_stream_t stream; + snd_rawmidi_info_t __user *info = argp; + if (get_user(stream, &info->stream)) + return -EFAULT; + switch (stream) { + case SNDRV_RAWMIDI_STREAM_INPUT: + return snd_rawmidi_info_user(rfile->input, info); + case SNDRV_RAWMIDI_STREAM_OUTPUT: + return snd_rawmidi_info_user(rfile->output, info); + default: + return -EINVAL; + } + } + case SNDRV_RAWMIDI_IOCTL_PARAMS: + { + snd_rawmidi_params_t params; + if (copy_from_user(¶ms, argp, sizeof(snd_rawmidi_params_t))) + return -EFAULT; + switch (params.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + return snd_rawmidi_output_params(rfile->output, ¶ms); + case SNDRV_RAWMIDI_STREAM_INPUT: + if (rfile->input == NULL) + return -EINVAL; + return snd_rawmidi_input_params(rfile->input, ¶ms); + default: + return -EINVAL; + } + } + case SNDRV_RAWMIDI_IOCTL_STATUS: + { + int err = 0; + snd_rawmidi_status_t status; + if (copy_from_user(&status, argp, sizeof(snd_rawmidi_status_t))) + return -EFAULT; + switch (status.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + err = snd_rawmidi_output_status(rfile->output, &status); + break; + case SNDRV_RAWMIDI_STREAM_INPUT: + if (rfile->input == NULL) + return -EINVAL; + err = snd_rawmidi_input_status(rfile->input, &status); + break; + default: + return -EINVAL; + } + if (err < 0) + return err; + if (copy_to_user(argp, &status, sizeof(snd_rawmidi_status_t))) + return -EFAULT; + return 0; + } + case SNDRV_RAWMIDI_IOCTL_DROP: + { + int val; + if (get_user(val, (int __user *) argp)) + return -EFAULT; + switch (val) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + return snd_rawmidi_drop_output(rfile->output); + default: + return -EINVAL; + } + } + case SNDRV_RAWMIDI_IOCTL_DRAIN: + { + int val; + if (get_user(val, (int __user *) argp)) + return -EFAULT; + switch (val) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + return snd_rawmidi_drain_output(rfile->output); + case SNDRV_RAWMIDI_STREAM_INPUT: + if (rfile->input == NULL) + return -EINVAL; + return snd_rawmidi_drain_input(rfile->input); + default: + return -EINVAL; + } + } +#ifdef CONFIG_SND_DEBUG + default: + snd_printk(KERN_WARNING "rawmidi: unknown command = 0x%x\n", cmd); +#endif + } + return -ENOTTY; +} + +static int snd_rawmidi_control_ioctl(snd_card_t * card, + snd_ctl_file_t * control, + unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + unsigned int tmp; + + tmp = card->number * SNDRV_RAWMIDI_DEVICES; + switch (cmd) { + case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE: + { + int device; + + if (get_user(device, (int __user *)argp)) + return -EFAULT; + device = device < 0 ? 0 : device + 1; + while (device < SNDRV_RAWMIDI_DEVICES) { + if (snd_rawmidi_devices[tmp + device]) + break; + device++; + } + if (device == SNDRV_RAWMIDI_DEVICES) + device = -1; + if (put_user(device, (int __user *)argp)) + return -EFAULT; + return 0; + } + case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE: + { + int val; + + if (get_user(val, (int __user *)argp)) + return -EFAULT; + control->prefer_rawmidi_subdevice = val; + return 0; + } + case SNDRV_CTL_IOCTL_RAWMIDI_INFO: + return snd_rawmidi_info_select_user(card, argp); + } + return -ENOIOCTLCMD; +} + +/** + * snd_rawmidi_receive - receive the input data from the device + * @substream: the rawmidi substream + * @buffer: the buffer pointer + * @count: the data size to read + * + * Reads the data from the internal buffer. + * + * Returns the size of read data, or a negative error code on failure. + */ +int snd_rawmidi_receive(snd_rawmidi_substream_t * substream, const unsigned char *buffer, int count) +{ + unsigned long flags; + int result = 0, count1; + snd_rawmidi_runtime_t *runtime = substream->runtime; + + if (runtime->buffer == NULL) { + snd_printd("snd_rawmidi_receive: input is not active!!!\n"); + return -EINVAL; + } + spin_lock_irqsave(&runtime->lock, flags); + if (count == 1) { /* special case, faster code */ + substream->bytes++; + if (runtime->avail < runtime->buffer_size) { + runtime->buffer[runtime->hw_ptr++] = buffer[0]; + runtime->hw_ptr %= runtime->buffer_size; + runtime->avail++; + result++; + } else { + runtime->xruns++; + } + } else { + substream->bytes += count; + count1 = runtime->buffer_size - runtime->hw_ptr; + if (count1 > count) + count1 = count; + if (count1 > (int)(runtime->buffer_size - runtime->avail)) + count1 = runtime->buffer_size - runtime->avail; + memcpy(runtime->buffer + runtime->hw_ptr, buffer, count1); + runtime->hw_ptr += count1; + runtime->hw_ptr %= runtime->buffer_size; + runtime->avail += count1; + count -= count1; + result += count1; + if (count > 0) { + buffer += count1; + count1 = count; + if (count1 > (int)(runtime->buffer_size - runtime->avail)) { + count1 = runtime->buffer_size - runtime->avail; + runtime->xruns += count - count1; + } + if (count1 > 0) { + memcpy(runtime->buffer, buffer, count1); + runtime->hw_ptr = count1; + runtime->avail += count1; + result += count1; + } + } + } + if (result > 0) { + if (runtime->event) + tasklet_hi_schedule(&runtime->tasklet); + else if (snd_rawmidi_ready(substream)) + wake_up(&runtime->sleep); + } + spin_unlock_irqrestore(&runtime->lock, flags); + return result; +} + +static long snd_rawmidi_kernel_read1(snd_rawmidi_substream_t *substream, + unsigned char *buf, long count, int kernel) +{ + unsigned long flags; + long result = 0, count1; + snd_rawmidi_runtime_t *runtime = substream->runtime; + + while (count > 0 && runtime->avail) { + count1 = runtime->buffer_size - runtime->appl_ptr; + if (count1 > count) + count1 = count; + spin_lock_irqsave(&runtime->lock, flags); + if (count1 > (int)runtime->avail) + count1 = runtime->avail; + if (kernel) { + memcpy(buf + result, runtime->buffer + runtime->appl_ptr, count1); + } else { + spin_unlock_irqrestore(&runtime->lock, flags); + if (copy_to_user((char __user *)buf + result, + runtime->buffer + runtime->appl_ptr, count1)) { + return result > 0 ? result : -EFAULT; + } + spin_lock_irqsave(&runtime->lock, flags); + } + runtime->appl_ptr += count1; + runtime->appl_ptr %= runtime->buffer_size; + runtime->avail -= count1; + spin_unlock_irqrestore(&runtime->lock, flags); + result += count1; + count -= count1; + } + return result; +} + +long snd_rawmidi_kernel_read(snd_rawmidi_substream_t *substream, unsigned char *buf, long count) +{ + snd_rawmidi_input_trigger(substream, 1); + return snd_rawmidi_kernel_read1(substream, buf, count, 1); +} + +static ssize_t snd_rawmidi_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + long result; + int count1; + snd_rawmidi_file_t *rfile; + snd_rawmidi_substream_t *substream; + snd_rawmidi_runtime_t *runtime; + + rfile = file->private_data; + substream = rfile->input; + if (substream == NULL) + return -EIO; + runtime = substream->runtime; + snd_rawmidi_input_trigger(substream, 1); + result = 0; + while (count > 0) { + spin_lock_irq(&runtime->lock); + while (!snd_rawmidi_ready(substream)) { + wait_queue_t wait; + if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { + spin_unlock_irq(&runtime->lock); + return result > 0 ? result : -EAGAIN; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&runtime->lock); + schedule(); + remove_wait_queue(&runtime->sleep, &wait); + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + if (!runtime->avail) + return result > 0 ? result : -EIO; + spin_lock_irq(&runtime->lock); + } + spin_unlock_irq(&runtime->lock); + count1 = snd_rawmidi_kernel_read1(substream, (unsigned char *)buf, count, 0); + if (count1 < 0) + return result > 0 ? result : count1; + result += count1; + buf += count1; + count -= count1; + } + return result; +} + +/** + * snd_rawmidi_transmit_empty - check whether the output buffer is empty + * @substream: the rawmidi substream + * + * Returns 1 if the internal output buffer is empty, 0 if not. + */ +int snd_rawmidi_transmit_empty(snd_rawmidi_substream_t * substream) +{ + snd_rawmidi_runtime_t *runtime = substream->runtime; + int result; + unsigned long flags; + + if (runtime->buffer == NULL) { + snd_printd("snd_rawmidi_transmit_empty: output is not active!!!\n"); + return 1; + } + spin_lock_irqsave(&runtime->lock, flags); + result = runtime->avail >= runtime->buffer_size; + spin_unlock_irqrestore(&runtime->lock, flags); + return result; +} + +/** + * snd_rawmidi_transmit_peek - copy data from the internal buffer + * @substream: the rawmidi substream + * @buffer: the buffer pointer + * @count: data size to transfer + * + * Copies data from the internal output buffer to the given buffer. + * + * Call this in the interrupt handler when the midi output is ready, + * and call snd_rawmidi_transmit_ack() after the transmission is + * finished. + * + * Returns the size of copied data, or a negative error code on failure. + */ +int snd_rawmidi_transmit_peek(snd_rawmidi_substream_t * substream, unsigned char *buffer, int count) +{ + unsigned long flags; + int result, count1; + snd_rawmidi_runtime_t *runtime = substream->runtime; + + if (runtime->buffer == NULL) { + snd_printd("snd_rawmidi_transmit_peek: output is not active!!!\n"); + return -EINVAL; + } + result = 0; + spin_lock_irqsave(&runtime->lock, flags); + if (runtime->avail >= runtime->buffer_size) { + /* warning: lowlevel layer MUST trigger down the hardware */ + goto __skip; + } + if (count == 1) { /* special case, faster code */ + *buffer = runtime->buffer[runtime->hw_ptr]; + result++; + } else { + count1 = runtime->buffer_size - runtime->hw_ptr; + if (count1 > count) + count1 = count; + if (count1 > (int)(runtime->buffer_size - runtime->avail)) + count1 = runtime->buffer_size - runtime->avail; + memcpy(buffer, runtime->buffer + runtime->hw_ptr, count1); + count -= count1; + result += count1; + if (count > 0) { + if (count > (int)(runtime->buffer_size - runtime->avail - count1)) + count = runtime->buffer_size - runtime->avail - count1; + memcpy(buffer + count1, runtime->buffer, count); + result += count; + } + } + __skip: + spin_unlock_irqrestore(&runtime->lock, flags); + return result; +} + +/** + * snd_rawmidi_transmit_ack - acknowledge the transmission + * @substream: the rawmidi substream + * @count: the tranferred count + * + * Advances the hardware pointer for the internal output buffer with + * the given size and updates the condition. + * Call after the transmission is finished. + * + * Returns the advanced size if successful, or a negative error code on failure. + */ +int snd_rawmidi_transmit_ack(snd_rawmidi_substream_t * substream, int count) +{ + unsigned long flags; + snd_rawmidi_runtime_t *runtime = substream->runtime; + + if (runtime->buffer == NULL) { + snd_printd("snd_rawmidi_transmit_ack: output is not active!!!\n"); + return -EINVAL; + } + spin_lock_irqsave(&runtime->lock, flags); + snd_assert(runtime->avail + count <= runtime->buffer_size, ); + runtime->hw_ptr += count; + runtime->hw_ptr %= runtime->buffer_size; + runtime->avail += count; + substream->bytes += count; + if (count > 0) { + if (runtime->drain || snd_rawmidi_ready(substream)) + wake_up(&runtime->sleep); + } + spin_unlock_irqrestore(&runtime->lock, flags); + return count; +} + +/** + * snd_rawmidi_transmit - copy from the buffer to the device + * @substream: the rawmidi substream + * @buf: the buffer pointer + * @count: the data size to transfer + * + * Copies data from the buffer to the device and advances the pointer. + * + * Returns the copied size if successful, or a negative error code on failure. + */ +int snd_rawmidi_transmit(snd_rawmidi_substream_t * substream, unsigned char *buffer, int count) +{ + count = snd_rawmidi_transmit_peek(substream, buffer, count); + if (count < 0) + return count; + return snd_rawmidi_transmit_ack(substream, count); +} + +static long snd_rawmidi_kernel_write1(snd_rawmidi_substream_t * substream, const unsigned char *buf, long count, int kernel) +{ + unsigned long flags; + long count1, result; + snd_rawmidi_runtime_t *runtime = substream->runtime; + + snd_assert(buf != NULL, return -EINVAL); + snd_assert(runtime->buffer != NULL, return -EINVAL); + + result = 0; + spin_lock_irqsave(&runtime->lock, flags); + if (substream->append) { + if ((long)runtime->avail < count) { + spin_unlock_irqrestore(&runtime->lock, flags); + return -EAGAIN; + } + } + while (count > 0 && runtime->avail > 0) { + count1 = runtime->buffer_size - runtime->appl_ptr; + if (count1 > count) + count1 = count; + if (count1 > (long)runtime->avail) + count1 = runtime->avail; + if (kernel) { + memcpy(runtime->buffer + runtime->appl_ptr, buf, count1); + } else { + spin_unlock_irqrestore(&runtime->lock, flags); + if (copy_from_user(runtime->buffer + runtime->appl_ptr, + (char __user *)buf, count1)) { + spin_lock_irqsave(&runtime->lock, flags); + result = result > 0 ? result : -EFAULT; + goto __end; + } + spin_lock_irqsave(&runtime->lock, flags); + } + runtime->appl_ptr += count1; + runtime->appl_ptr %= runtime->buffer_size; + runtime->avail -= count1; + result += count1; + buf += count1; + count -= count1; + } + __end: + count1 = runtime->avail < runtime->buffer_size; + spin_unlock_irqrestore(&runtime->lock, flags); + if (count1) + snd_rawmidi_output_trigger(substream, 1); + return result; +} + +long snd_rawmidi_kernel_write(snd_rawmidi_substream_t * substream, const unsigned char *buf, long count) +{ + return snd_rawmidi_kernel_write1(substream, buf, count, 1); +} + +static ssize_t snd_rawmidi_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ + long result, timeout; + int count1; + snd_rawmidi_file_t *rfile; + snd_rawmidi_runtime_t *runtime; + snd_rawmidi_substream_t *substream; + + rfile = file->private_data; + substream = rfile->output; + runtime = substream->runtime; + /* we cannot put an atomic message to our buffer */ + if (substream->append && count > runtime->buffer_size) + return -EIO; + result = 0; + while (count > 0) { + spin_lock_irq(&runtime->lock); + while (!snd_rawmidi_ready_append(substream, count)) { + wait_queue_t wait; + if (file->f_flags & O_NONBLOCK) { + spin_unlock_irq(&runtime->lock); + return result > 0 ? result : -EAGAIN; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&runtime->lock); + timeout = schedule_timeout(30 * HZ); + remove_wait_queue(&runtime->sleep, &wait); + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + if (!runtime->avail && !timeout) + return result > 0 ? result : -EIO; + spin_lock_irq(&runtime->lock); + } + spin_unlock_irq(&runtime->lock); + count1 = snd_rawmidi_kernel_write1(substream, (unsigned char *)buf, count, 0); + if (count1 < 0) + return result > 0 ? result : count1; + result += count1; + buf += count1; + if ((size_t)count1 < count && (file->f_flags & O_NONBLOCK)) + break; + count -= count1; + } + if (file->f_flags & O_SYNC) { + spin_lock_irq(&runtime->lock); + while (runtime->avail != runtime->buffer_size) { + wait_queue_t wait; + unsigned int last_avail = runtime->avail; + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&runtime->lock); + timeout = schedule_timeout(30 * HZ); + remove_wait_queue(&runtime->sleep, &wait); + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + if (runtime->avail == last_avail && !timeout) + return result > 0 ? result : -EIO; + spin_lock_irq(&runtime->lock); + } + spin_unlock_irq(&runtime->lock); + } + return result; +} + +static unsigned int snd_rawmidi_poll(struct file *file, poll_table * wait) +{ + snd_rawmidi_file_t *rfile; + snd_rawmidi_runtime_t *runtime; + unsigned int mask; + + rfile = file->private_data; + if (rfile->input != NULL) { + runtime = rfile->input->runtime; + snd_rawmidi_input_trigger(rfile->input, 1); + poll_wait(file, &runtime->sleep, wait); + } + if (rfile->output != NULL) { + runtime = rfile->output->runtime; + poll_wait(file, &runtime->sleep, wait); + } + mask = 0; + if (rfile->input != NULL) { + if (snd_rawmidi_ready(rfile->input)) + mask |= POLLIN | POLLRDNORM; + } + if (rfile->output != NULL) { + if (snd_rawmidi_ready(rfile->output)) + mask |= POLLOUT | POLLWRNORM; + } + return mask; +} + +/* + */ +#ifdef CONFIG_COMPAT +#include "rawmidi_compat.c" +#else +#define snd_rawmidi_ioctl_compat NULL +#endif + +/* + + */ + +static void snd_rawmidi_proc_info_read(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + snd_rawmidi_t *rmidi; + snd_rawmidi_substream_t *substream; + snd_rawmidi_runtime_t *runtime; + struct list_head *list; + + rmidi = entry->private_data; + snd_iprintf(buffer, "%s\n\n", rmidi->name); + down(&rmidi->open_mutex); + if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) { + list_for_each(list, &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) { + substream = list_entry(list, snd_rawmidi_substream_t, list); + snd_iprintf(buffer, + "Output %d\n" + " Tx bytes : %lu\n", + substream->number, + (unsigned long) substream->bytes); + if (substream->opened) { + runtime = substream->runtime; + snd_iprintf(buffer, + " Mode : %s\n" + " Buffer size : %lu\n" + " Avail : %lu\n", + runtime->oss ? "OSS compatible" : "native", + (unsigned long) runtime->buffer_size, + (unsigned long) runtime->avail); + } + } + } + if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_INPUT) { + list_for_each(list, &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) { + substream = list_entry(list, snd_rawmidi_substream_t, list); + snd_iprintf(buffer, + "Input %d\n" + " Rx bytes : %lu\n", + substream->number, + (unsigned long) substream->bytes); + if (substream->opened) { + runtime = substream->runtime; + snd_iprintf(buffer, + " Buffer size : %lu\n" + " Avail : %lu\n" + " Overruns : %lu\n", + (unsigned long) runtime->buffer_size, + (unsigned long) runtime->avail, + (unsigned long) runtime->xruns); + } + } + } + up(&rmidi->open_mutex); +} + +/* + * Register functions + */ + +static struct file_operations snd_rawmidi_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_rawmidi_read, + .write = snd_rawmidi_write, + .open = snd_rawmidi_open, + .release = snd_rawmidi_release, + .poll = snd_rawmidi_poll, + .unlocked_ioctl = snd_rawmidi_ioctl, + .compat_ioctl = snd_rawmidi_ioctl_compat, +}; + +static snd_minor_t snd_rawmidi_reg = +{ + .comment = "raw midi", + .f_ops = &snd_rawmidi_f_ops, +}; + +static int snd_rawmidi_alloc_substreams(snd_rawmidi_t *rmidi, + snd_rawmidi_str_t *stream, + int direction, + int count) +{ + snd_rawmidi_substream_t *substream; + int idx; + + INIT_LIST_HEAD(&stream->substreams); + for (idx = 0; idx < count; idx++) { + substream = kcalloc(1, sizeof(*substream), GFP_KERNEL); + if (substream == NULL) + return -ENOMEM; + substream->stream = direction; + substream->number = idx; + substream->rmidi = rmidi; + substream->pstr = stream; + list_add_tail(&substream->list, &stream->substreams); + stream->substream_count++; + } + return 0; +} + +/** + * snd_rawmidi_new - create a rawmidi instance + * @card: the card instance + * @id: the id string + * @device: the device index + * @output_count: the number of output streams + * @input_count: the number of input streams + * @rrawmidi: the pointer to store the new rawmidi instance + * + * Creates a new rawmidi instance. + * Use snd_rawmidi_set_ops() to set the operators to the new instance. + * + * Returns zero if successful, or a negative error code on failure. + */ +int snd_rawmidi_new(snd_card_t * card, char *id, int device, + int output_count, int input_count, + snd_rawmidi_t ** rrawmidi) +{ + snd_rawmidi_t *rmidi; + int err; + static snd_device_ops_t ops = { + .dev_free = snd_rawmidi_dev_free, + .dev_register = snd_rawmidi_dev_register, + .dev_disconnect = snd_rawmidi_dev_disconnect, + .dev_unregister = snd_rawmidi_dev_unregister + }; + + snd_assert(rrawmidi != NULL, return -EINVAL); + *rrawmidi = NULL; + snd_assert(card != NULL, return -ENXIO); + rmidi = kcalloc(1, sizeof(*rmidi), GFP_KERNEL); + if (rmidi == NULL) + return -ENOMEM; + rmidi->card = card; + rmidi->device = device; + init_MUTEX(&rmidi->open_mutex); + init_waitqueue_head(&rmidi->open_wait); + if (id != NULL) + strlcpy(rmidi->id, id, sizeof(rmidi->id)); + if ((err = snd_rawmidi_alloc_substreams(rmidi, &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT], SNDRV_RAWMIDI_STREAM_INPUT, input_count)) < 0) { + snd_rawmidi_free(rmidi); + return err; + } + if ((err = snd_rawmidi_alloc_substreams(rmidi, &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT], SNDRV_RAWMIDI_STREAM_OUTPUT, output_count)) < 0) { + snd_rawmidi_free(rmidi); + return err; + } + if ((err = snd_device_new(card, SNDRV_DEV_RAWMIDI, rmidi, &ops)) < 0) { + snd_rawmidi_free(rmidi); + return err; + } + *rrawmidi = rmidi; + return 0; +} + +static void snd_rawmidi_free_substreams(snd_rawmidi_str_t *stream) +{ + snd_rawmidi_substream_t *substream; + + while (!list_empty(&stream->substreams)) { + substream = list_entry(stream->substreams.next, snd_rawmidi_substream_t, list); + list_del(&substream->list); + kfree(substream); + } +} + +static int snd_rawmidi_free(snd_rawmidi_t *rmidi) +{ + snd_assert(rmidi != NULL, return -ENXIO); + snd_rawmidi_free_substreams(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]); + snd_rawmidi_free_substreams(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]); + if (rmidi->private_free) + rmidi->private_free(rmidi); + kfree(rmidi); + return 0; +} + +static int snd_rawmidi_dev_free(snd_device_t *device) +{ + snd_rawmidi_t *rmidi = device->device_data; + return snd_rawmidi_free(rmidi); +} + +#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) +static void snd_rawmidi_dev_seq_free(snd_seq_device_t *device) +{ + snd_rawmidi_t *rmidi = device->private_data; + rmidi->seq_dev = NULL; +} +#endif + +static int snd_rawmidi_dev_register(snd_device_t *device) +{ + int idx, err; + snd_info_entry_t *entry; + char name[16]; + snd_rawmidi_t *rmidi = device->device_data; + + if (rmidi->device >= SNDRV_RAWMIDI_DEVICES) + return -ENOMEM; + down(®ister_mutex); + idx = (rmidi->card->number * SNDRV_RAWMIDI_DEVICES) + rmidi->device; + if (snd_rawmidi_devices[idx] != NULL) { + up(®ister_mutex); + return -EBUSY; + } + snd_rawmidi_devices[idx] = rmidi; + sprintf(name, "midiC%iD%i", rmidi->card->number, rmidi->device); + if ((err = snd_register_device(SNDRV_DEVICE_TYPE_RAWMIDI, + rmidi->card, rmidi->device, + &snd_rawmidi_reg, name)) < 0) { + snd_printk(KERN_ERR "unable to register rawmidi device %i:%i\n", rmidi->card->number, rmidi->device); + snd_rawmidi_devices[idx] = NULL; + up(®ister_mutex); + return err; + } + if (rmidi->ops && rmidi->ops->dev_register && + (err = rmidi->ops->dev_register(rmidi)) < 0) { + snd_unregister_device(SNDRV_DEVICE_TYPE_RAWMIDI, rmidi->card, rmidi->device); + snd_rawmidi_devices[idx] = NULL; + up(®ister_mutex); + return err; + } +#ifdef CONFIG_SND_OSSEMUL + rmidi->ossreg = 0; + if ((int)rmidi->device == midi_map[rmidi->card->number]) { + if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, + rmidi->card, 0, &snd_rawmidi_reg, name) < 0) { + snd_printk(KERN_ERR "unable to register OSS rawmidi device %i:%i\n", rmidi->card->number, 0); + } else { + rmidi->ossreg++; +#ifdef SNDRV_OSS_INFO_DEV_MIDI + snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIDI, rmidi->card->number, rmidi->name); +#endif + } + } + if ((int)rmidi->device == amidi_map[rmidi->card->number]) { + if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, + rmidi->card, 1, &snd_rawmidi_reg, name) < 0) { + snd_printk(KERN_ERR "unable to register OSS rawmidi device %i:%i\n", rmidi->card->number, 1); + } else { + rmidi->ossreg++; + } + } +#endif /* CONFIG_SND_OSSEMUL */ + up(®ister_mutex); + sprintf(name, "midi%d", rmidi->device); + entry = snd_info_create_card_entry(rmidi->card, name, rmidi->card->proc_root); + if (entry) { + entry->private_data = rmidi; + entry->c.text.read_size = 1024; + entry->c.text.read = snd_rawmidi_proc_info_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + rmidi->proc_entry = entry; +#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) + if (!rmidi->ops || !rmidi->ops->dev_register) { /* own registration mechanism */ + if (snd_seq_device_new(rmidi->card, rmidi->device, SNDRV_SEQ_DEV_ID_MIDISYNTH, 0, &rmidi->seq_dev) >= 0) { + rmidi->seq_dev->private_data = rmidi; + rmidi->seq_dev->private_free = snd_rawmidi_dev_seq_free; + sprintf(rmidi->seq_dev->name, "MIDI %d-%d", rmidi->card->number, rmidi->device); + snd_device_register(rmidi->card, rmidi->seq_dev); + } + } +#endif + return 0; +} + +static int snd_rawmidi_dev_disconnect(snd_device_t *device) +{ + snd_rawmidi_t *rmidi = device->device_data; + int idx; + + down(®ister_mutex); + idx = (rmidi->card->number * SNDRV_RAWMIDI_DEVICES) + rmidi->device; + snd_rawmidi_devices[idx] = NULL; + up(®ister_mutex); + return 0; +} + +static int snd_rawmidi_dev_unregister(snd_device_t *device) +{ + int idx; + snd_rawmidi_t *rmidi = device->device_data; + + snd_assert(rmidi != NULL, return -ENXIO); + down(®ister_mutex); + idx = (rmidi->card->number * SNDRV_RAWMIDI_DEVICES) + rmidi->device; + snd_rawmidi_devices[idx] = NULL; + if (rmidi->proc_entry) { + snd_info_unregister(rmidi->proc_entry); + rmidi->proc_entry = NULL; + } +#ifdef CONFIG_SND_OSSEMUL + if (rmidi->ossreg) { + if ((int)rmidi->device == midi_map[rmidi->card->number]) { + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 0); +#ifdef SNDRV_OSS_INFO_DEV_MIDI + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIDI, rmidi->card->number); +#endif + } + if ((int)rmidi->device == amidi_map[rmidi->card->number]) + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 1); + rmidi->ossreg = 0; + } +#endif /* CONFIG_SND_OSSEMUL */ + if (rmidi->ops && rmidi->ops->dev_unregister) + rmidi->ops->dev_unregister(rmidi); + snd_unregister_device(SNDRV_DEVICE_TYPE_RAWMIDI, rmidi->card, rmidi->device); + up(®ister_mutex); +#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) + if (rmidi->seq_dev) { + snd_device_free(rmidi->card, rmidi->seq_dev); + rmidi->seq_dev = NULL; + } +#endif + return snd_rawmidi_free(rmidi); +} + +/** + * snd_rawmidi_set_ops - set the rawmidi operators + * @rmidi: the rawmidi instance + * @stream: the stream direction, SNDRV_RAWMIDI_STREAM_XXX + * @ops: the operator table + * + * Sets the rawmidi operators for the given stream direction. + */ +void snd_rawmidi_set_ops(snd_rawmidi_t *rmidi, int stream, snd_rawmidi_ops_t *ops) +{ + struct list_head *list; + snd_rawmidi_substream_t *substream; + + list_for_each(list, &rmidi->streams[stream].substreams) { + substream = list_entry(list, snd_rawmidi_substream_t, list); + substream->ops = ops; + } +} + +/* + * ENTRY functions + */ + +static int __init alsa_rawmidi_init(void) +{ + + snd_ctl_register_ioctl(snd_rawmidi_control_ioctl); + snd_ctl_register_ioctl_compat(snd_rawmidi_control_ioctl); +#ifdef CONFIG_SND_OSSEMUL + { int i; + /* check device map table */ + for (i = 0; i < SNDRV_CARDS; i++) { + if (midi_map[i] < 0 || midi_map[i] >= SNDRV_RAWMIDI_DEVICES) { + snd_printk(KERN_ERR "invalid midi_map[%d] = %d\n", i, midi_map[i]); + midi_map[i] = 0; + } + if (amidi_map[i] < 0 || amidi_map[i] >= SNDRV_RAWMIDI_DEVICES) { + snd_printk(KERN_ERR "invalid amidi_map[%d] = %d\n", i, amidi_map[i]); + amidi_map[i] = 1; + } + } + } +#endif /* CONFIG_SND_OSSEMUL */ + return 0; +} + +static void __exit alsa_rawmidi_exit(void) +{ + snd_ctl_unregister_ioctl(snd_rawmidi_control_ioctl); + snd_ctl_unregister_ioctl_compat(snd_rawmidi_control_ioctl); +} + +module_init(alsa_rawmidi_init) +module_exit(alsa_rawmidi_exit) + +EXPORT_SYMBOL(snd_rawmidi_output_params); +EXPORT_SYMBOL(snd_rawmidi_input_params); +EXPORT_SYMBOL(snd_rawmidi_drop_output); +EXPORT_SYMBOL(snd_rawmidi_drain_output); +EXPORT_SYMBOL(snd_rawmidi_drain_input); +EXPORT_SYMBOL(snd_rawmidi_receive); +EXPORT_SYMBOL(snd_rawmidi_transmit_empty); +EXPORT_SYMBOL(snd_rawmidi_transmit_peek); +EXPORT_SYMBOL(snd_rawmidi_transmit_ack); +EXPORT_SYMBOL(snd_rawmidi_transmit); +EXPORT_SYMBOL(snd_rawmidi_new); +EXPORT_SYMBOL(snd_rawmidi_set_ops); +EXPORT_SYMBOL(snd_rawmidi_info); +EXPORT_SYMBOL(snd_rawmidi_info_select); +EXPORT_SYMBOL(snd_rawmidi_kernel_open); +EXPORT_SYMBOL(snd_rawmidi_kernel_release); +EXPORT_SYMBOL(snd_rawmidi_kernel_read); +EXPORT_SYMBOL(snd_rawmidi_kernel_write); diff --git a/sound/core/rawmidi_compat.c b/sound/core/rawmidi_compat.c new file mode 100644 index 000000000000..d97631c3f3ad --- /dev/null +++ b/sound/core/rawmidi_compat.c @@ -0,0 +1,120 @@ +/* + * 32bit -> 64bit ioctl wrapper for raw MIDI API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This file included from rawmidi.c */ + +#include <linux/compat.h> + +struct sndrv_rawmidi_params32 { + s32 stream; + u32 buffer_size; + u32 avail_min; + unsigned int no_active_sensing; /* avoid bit-field */ + unsigned char reserved[16]; +} __attribute__((packed)); + +static int snd_rawmidi_ioctl_params_compat(snd_rawmidi_file_t *rfile, + struct sndrv_rawmidi_params32 __user *src) +{ + snd_rawmidi_params_t params; + unsigned int val; + + if (rfile->output == NULL) + return -EINVAL; + if (get_user(params.stream, &src->stream) || + get_user(params.buffer_size, &src->buffer_size) || + get_user(params.avail_min, &src->avail_min) || + get_user(val, &src->no_active_sensing)) + return -EFAULT; + params.no_active_sensing = val; + switch (params.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + return snd_rawmidi_output_params(rfile->output, ¶ms); + case SNDRV_RAWMIDI_STREAM_INPUT: + return snd_rawmidi_input_params(rfile->input, ¶ms); + } + return -EINVAL; +} + +struct sndrv_rawmidi_status32 { + s32 stream; + struct compat_timespec tstamp; + u32 avail; + u32 xruns; + unsigned char reserved[16]; +} __attribute__((packed)); + +static int snd_rawmidi_ioctl_status_compat(snd_rawmidi_file_t *rfile, + struct sndrv_rawmidi_status32 __user *src) +{ + int err; + snd_rawmidi_status_t status; + + if (rfile->output == NULL) + return -EINVAL; + if (get_user(status.stream, &src->stream)) + return -EFAULT; + + switch (status.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + err = snd_rawmidi_output_status(rfile->output, &status); + break; + case SNDRV_RAWMIDI_STREAM_INPUT: + err = snd_rawmidi_input_status(rfile->input, &status); + break; + default: + return -EINVAL; + } + if (err < 0) + return err; + + if (put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) || + put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) || + put_user(status.avail, &src->avail) || + put_user(status.xruns, &src->xruns)) + return -EFAULT; + + return 0; +} + +enum { + SNDRV_RAWMIDI_IOCTL_PARAMS32 = _IOWR('W', 0x10, struct sndrv_rawmidi_params32), + SNDRV_RAWMIDI_IOCTL_STATUS32 = _IOWR('W', 0x20, struct sndrv_rawmidi_status32), +}; + +static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_rawmidi_file_t *rfile; + void __user *argp = compat_ptr(arg); + + rfile = file->private_data; + switch (cmd) { + case SNDRV_RAWMIDI_IOCTL_PVERSION: + case SNDRV_RAWMIDI_IOCTL_INFO: + case SNDRV_RAWMIDI_IOCTL_DROP: + case SNDRV_RAWMIDI_IOCTL_DRAIN: + return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_RAWMIDI_IOCTL_PARAMS32: + return snd_rawmidi_ioctl_params_compat(rfile, argp); + case SNDRV_RAWMIDI_IOCTL_STATUS32: + return snd_rawmidi_ioctl_status_compat(rfile, argp); + } + return -ENOIOCTLCMD; +} diff --git a/sound/core/rtctimer.c b/sound/core/rtctimer.c new file mode 100644 index 000000000000..bd5d584d284d --- /dev/null +++ b/sound/core/rtctimer.c @@ -0,0 +1,188 @@ +/* + * RTC based high-frequency timer + * + * Copyright (C) 2000 Takashi Iwai + * based on rtctimer.c by Steve Ratcliffe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/threads.h> +#include <linux/interrupt.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/timer.h> +#include <sound/info.h> + +#if defined(CONFIG_RTC) || defined(CONFIG_RTC_MODULE) + +#include <linux/mc146818rtc.h> + +#define RTC_FREQ 1024 /* default frequency */ +#define NANO_SEC 1000000000L /* 10^9 in sec */ + +/* + * prototypes + */ +static int rtctimer_open(snd_timer_t *t); +static int rtctimer_close(snd_timer_t *t); +static int rtctimer_start(snd_timer_t *t); +static int rtctimer_stop(snd_timer_t *t); + + +/* + * The hardware dependent description for this timer. + */ +static struct _snd_timer_hardware rtc_hw = { + .flags = SNDRV_TIMER_HW_FIRST|SNDRV_TIMER_HW_AUTO, + .ticks = 100000000L, /* FIXME: XXX */ + .open = rtctimer_open, + .close = rtctimer_close, + .start = rtctimer_start, + .stop = rtctimer_stop, +}; + +static int rtctimer_freq = RTC_FREQ; /* frequency */ +static snd_timer_t *rtctimer; +static atomic_t rtc_inc = ATOMIC_INIT(0); +static rtc_task_t rtc_task; + + +static int +rtctimer_open(snd_timer_t *t) +{ + int err; + + err = rtc_register(&rtc_task); + if (err < 0) + return err; + t->private_data = &rtc_task; + return 0; +} + +static int +rtctimer_close(snd_timer_t *t) +{ + rtc_task_t *rtc = t->private_data; + if (rtc) { + rtc_unregister(rtc); + t->private_data = NULL; + } + return 0; +} + +static int +rtctimer_start(snd_timer_t *timer) +{ + rtc_task_t *rtc = timer->private_data; + snd_assert(rtc != NULL, return -EINVAL); + rtc_control(rtc, RTC_IRQP_SET, rtctimer_freq); + rtc_control(rtc, RTC_PIE_ON, 0); + atomic_set(&rtc_inc, 0); + return 0; +} + +static int +rtctimer_stop(snd_timer_t *timer) +{ + rtc_task_t *rtc = timer->private_data; + snd_assert(rtc != NULL, return -EINVAL); + rtc_control(rtc, RTC_PIE_OFF, 0); + return 0; +} + +/* + * interrupt + */ +static void rtctimer_interrupt(void *private_data) +{ + int ticks; + + atomic_inc(&rtc_inc); + ticks = atomic_read(&rtc_inc); + snd_timer_interrupt((snd_timer_t*)private_data, ticks); + atomic_sub(ticks, &rtc_inc); +} + + +/* + * ENTRY functions + */ +static int __init rtctimer_init(void) +{ + int order, err; + snd_timer_t *timer; + + if (rtctimer_freq < 2 || rtctimer_freq > 8192) { + snd_printk(KERN_ERR "rtctimer: invalid frequency %d\n", rtctimer_freq); + return -EINVAL; + } + for (order = 1; rtctimer_freq > order; order <<= 1) + ; + if (rtctimer_freq != order) { + snd_printk(KERN_ERR "rtctimer: invalid frequency %d\n", rtctimer_freq); + return -EINVAL; + } + + /* Create a new timer and set up the fields */ + err = snd_timer_global_new("rtc", SNDRV_TIMER_GLOBAL_RTC, &timer); + if (err < 0) + return err; + + strcpy(timer->name, "RTC timer"); + timer->hw = rtc_hw; + timer->hw.resolution = NANO_SEC / rtctimer_freq; + + /* set up RTC callback */ + rtc_task.func = rtctimer_interrupt; + rtc_task.private_data = timer; + + err = snd_timer_global_register(timer); + if (err < 0) { + snd_timer_global_free(timer); + return err; + } + rtctimer = timer; /* remember this */ + + return 0; +} + +static void __exit rtctimer_exit(void) +{ + if (rtctimer) { + snd_timer_global_unregister(rtctimer); + rtctimer = NULL; + } +} + + +/* + * exported stuff + */ +module_init(rtctimer_init) +module_exit(rtctimer_exit) + +module_param(rtctimer_freq, int, 0444); +MODULE_PARM_DESC(rtctimer_freq, "timer frequency in Hz"); + +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_RTC)); + +#endif /* CONFIG_RTC || CONFIG_RTC_MODULE */ diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile new file mode 100644 index 000000000000..64cb50d7b589 --- /dev/null +++ b/sound/core/seq/Makefile @@ -0,0 +1,44 @@ +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> +# + +obj-$(CONFIG_SND) += instr/ +ifeq ($(CONFIG_SND_SEQUENCER_OSS),y) + obj-$(CONFIG_SND_SEQUENCER) += oss/ +endif + +snd-seq-device-objs := seq_device.o +snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \ + seq_fifo.o seq_prioq.o seq_timer.o \ + seq_system.o seq_ports.o seq_info.o +snd-seq-midi-objs := seq_midi.o +snd-seq-midi-emul-objs := seq_midi_emul.o +snd-seq-midi-event-objs := seq_midi_event.o +snd-seq-instr-objs := seq_instr.o +snd-seq-dummy-objs := seq_dummy.o +snd-seq-virmidi-objs := seq_virmidi.o + +# +# this function returns: +# "m" - CONFIG_SND_SEQUENCER is m +# <empty string> - CONFIG_SND_SEQUENCER is undefined +# otherwise parameter #1 value +# +sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) + +obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o snd-seq-device.o +ifeq ($(CONFIG_SND_SEQUENCER_OSS),y) +obj-$(CONFIG_SND_SEQUENCER) += snd-seq-midi-event.o +endif +obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_VIRMIDI) += snd-seq-virmidi.o snd-seq-midi-event.o +obj-$(call sequencer,$(CONFIG_SND_RAWMIDI)) += snd-seq-midi.o snd-seq-midi-event.o +obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-seq-midi-event.o snd-seq-midi-emul.o snd-seq-instr.o +obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-seq-midi-event.o snd-seq-midi-emul.o snd-seq-instr.o +obj-$(call sequencer,$(CONFIG_SND_GUS_SYNTH)) += snd-seq-instr.o +obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-seq-midi-emul.o snd-seq-virmidi.o +obj-$(call sequencer,$(CONFIG_SND_EMU10K1)) += snd-seq-midi-emul.o snd-seq-virmidi.o +obj-$(call sequencer,$(CONFIG_SND_TRIDENT)) += snd-seq-midi-emul.o snd-seq-instr.o diff --git a/sound/core/seq/instr/Makefile b/sound/core/seq/instr/Makefile new file mode 100644 index 000000000000..69138f30a293 --- /dev/null +++ b/sound/core/seq/instr/Makefile @@ -0,0 +1,23 @@ +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> +# + +snd-ainstr-fm-objs := ainstr_fm.o +snd-ainstr-simple-objs := ainstr_simple.o +snd-ainstr-gf1-objs := ainstr_gf1.o +snd-ainstr-iw-objs := ainstr_iw.o + +# +# this function returns: +# "m" - CONFIG_SND_SEQUENCER is m +# <empty string> - CONFIG_SND_SEQUENCER is undefined +# otherwise parameter #1 value +# +sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) + +# Toplevel Module Dependency +obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-ainstr-fm.o +obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-ainstr-fm.o +obj-$(call sequencer,$(CONFIG_SND_GUS_SYNTH)) += snd-ainstr-gf1.o snd-ainstr-simple.o snd-ainstr-iw.o +obj-$(call sequencer,$(CONFIG_SND_TRIDENT)) += snd-ainstr-simple.o diff --git a/sound/core/seq/instr/ainstr_fm.c b/sound/core/seq/instr/ainstr_fm.c new file mode 100644 index 000000000000..5c671e69884f --- /dev/null +++ b/sound/core/seq/instr/ainstr_fm.c @@ -0,0 +1,156 @@ +/* + * FM (OPL2/3) Instrument routines + * Copyright (c) 2000 Uros Bizjak <uros@kss-loka.si> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <sound/core.h> +#include <sound/ainstr_fm.h> +#include <sound/initval.h> +#include <asm/uaccess.h> + +MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture FM Instrument support."); +MODULE_LICENSE("GPL"); + +static int snd_seq_fm_put(void *private_data, snd_seq_kinstr_t *instr, + char __user *instr_data, long len, int atomic, int cmd) +{ + fm_instrument_t *ip; + fm_xinstrument_t ix; + int idx; + + if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE) + return -EINVAL; + /* copy instrument data */ + if (len < (long)sizeof(ix)) + return -EINVAL; + if (copy_from_user(&ix, instr_data, sizeof(ix))) + return -EFAULT; + if (ix.stype != FM_STRU_INSTR) + return -EINVAL; + ip = (fm_instrument_t *)KINSTR_DATA(instr); + ip->share_id[0] = le32_to_cpu(ix.share_id[0]); + ip->share_id[1] = le32_to_cpu(ix.share_id[1]); + ip->share_id[2] = le32_to_cpu(ix.share_id[2]); + ip->share_id[3] = le32_to_cpu(ix.share_id[3]); + ip->type = ix.type; + for (idx = 0; idx < 4; idx++) { + ip->op[idx].am_vib = ix.op[idx].am_vib; + ip->op[idx].ksl_level = ix.op[idx].ksl_level; + ip->op[idx].attack_decay = ix.op[idx].attack_decay; + ip->op[idx].sustain_release = ix.op[idx].sustain_release; + ip->op[idx].wave_select = ix.op[idx].wave_select; + } + for (idx = 0; idx < 2; idx++) { + ip->feedback_connection[idx] = ix.feedback_connection[idx]; + } + ip->echo_delay = ix.echo_delay; + ip->echo_atten = ix.echo_atten; + ip->chorus_spread = ix.chorus_spread; + ip->trnsps = ix.trnsps; + ip->fix_dur = ix.fix_dur; + ip->modes = ix.modes; + ip->fix_key = ix.fix_key; + return 0; +} + +static int snd_seq_fm_get(void *private_data, snd_seq_kinstr_t *instr, + char __user *instr_data, long len, int atomic, + int cmd) +{ + fm_instrument_t *ip; + fm_xinstrument_t ix; + int idx; + + if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL) + return -EINVAL; + if (len < (long)sizeof(ix)) + return -ENOMEM; + memset(&ix, 0, sizeof(ix)); + ip = (fm_instrument_t *)KINSTR_DATA(instr); + ix.stype = FM_STRU_INSTR; + ix.share_id[0] = cpu_to_le32(ip->share_id[0]); + ix.share_id[1] = cpu_to_le32(ip->share_id[1]); + ix.share_id[2] = cpu_to_le32(ip->share_id[2]); + ix.share_id[3] = cpu_to_le32(ip->share_id[3]); + ix.type = ip->type; + for (idx = 0; idx < 4; idx++) { + ix.op[idx].am_vib = ip->op[idx].am_vib; + ix.op[idx].ksl_level = ip->op[idx].ksl_level; + ix.op[idx].attack_decay = ip->op[idx].attack_decay; + ix.op[idx].sustain_release = ip->op[idx].sustain_release; + ix.op[idx].wave_select = ip->op[idx].wave_select; + } + for (idx = 0; idx < 2; idx++) { + ix.feedback_connection[idx] = ip->feedback_connection[idx]; + } + if (copy_to_user(instr_data, &ix, sizeof(ix))) + return -EFAULT; + ix.echo_delay = ip->echo_delay; + ix.echo_atten = ip->echo_atten; + ix.chorus_spread = ip->chorus_spread; + ix.trnsps = ip->trnsps; + ix.fix_dur = ip->fix_dur; + ix.modes = ip->modes; + ix.fix_key = ip->fix_key; + return 0; +} + +static int snd_seq_fm_get_size(void *private_data, snd_seq_kinstr_t *instr, + long *size) +{ + *size = sizeof(fm_xinstrument_t); + return 0; +} + +int snd_seq_fm_init(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_ops_t *next) +{ + memset(ops, 0, sizeof(*ops)); + // ops->private_data = private_data; + ops->add_len = sizeof(fm_instrument_t); + ops->instr_type = SNDRV_SEQ_INSTR_ID_OPL2_3; + ops->put = snd_seq_fm_put; + ops->get = snd_seq_fm_get; + ops->get_size = snd_seq_fm_get_size; + // ops->remove = snd_seq_fm_remove; + // ops->notify = snd_seq_fm_notify; + ops->next = next; + return 0; +} + +/* + * Init part + */ + +static int __init alsa_ainstr_fm_init(void) +{ + return 0; +} + +static void __exit alsa_ainstr_fm_exit(void) +{ +} + +module_init(alsa_ainstr_fm_init) +module_exit(alsa_ainstr_fm_exit) + +EXPORT_SYMBOL(snd_seq_fm_init); diff --git a/sound/core/seq/instr/ainstr_gf1.c b/sound/core/seq/instr/ainstr_gf1.c new file mode 100644 index 000000000000..0779c41ca037 --- /dev/null +++ b/sound/core/seq/instr/ainstr_gf1.c @@ -0,0 +1,358 @@ +/* + * GF1 (GUS) Patch - Instrument routines + * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/ainstr_gf1.h> +#include <sound/initval.h> +#include <asm/uaccess.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture GF1 (GUS) Patch support."); +MODULE_LICENSE("GPL"); + +static unsigned int snd_seq_gf1_size(unsigned int size, unsigned int format) +{ + unsigned int result = size; + + if (format & GF1_WAVE_16BIT) + result <<= 1; + if (format & GF1_WAVE_STEREO) + result <<= 1; + return format; +} + +static int snd_seq_gf1_copy_wave_from_stream(snd_gf1_ops_t *ops, + gf1_instrument_t *ip, + char __user **data, + long *len, + int atomic) +{ + gf1_wave_t *wp, *prev; + gf1_xwave_t xp; + int err, gfp_mask; + unsigned int real_size; + + gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL; + if (*len < (long)sizeof(xp)) + return -EINVAL; + if (copy_from_user(&xp, *data, sizeof(xp))) + return -EFAULT; + *data += sizeof(xp); + *len -= sizeof(xp); + wp = kcalloc(1, sizeof(*wp), gfp_mask); + if (wp == NULL) + return -ENOMEM; + wp->share_id[0] = le32_to_cpu(xp.share_id[0]); + wp->share_id[1] = le32_to_cpu(xp.share_id[1]); + wp->share_id[2] = le32_to_cpu(xp.share_id[2]); + wp->share_id[3] = le32_to_cpu(xp.share_id[3]); + wp->format = le32_to_cpu(xp.format); + wp->size = le32_to_cpu(xp.size); + wp->start = le32_to_cpu(xp.start); + wp->loop_start = le32_to_cpu(xp.loop_start); + wp->loop_end = le32_to_cpu(xp.loop_end); + wp->loop_repeat = le16_to_cpu(xp.loop_repeat); + wp->flags = xp.flags; + wp->sample_rate = le32_to_cpu(xp.sample_rate); + wp->low_frequency = le32_to_cpu(xp.low_frequency); + wp->high_frequency = le32_to_cpu(xp.high_frequency); + wp->root_frequency = le32_to_cpu(xp.root_frequency); + wp->tune = le16_to_cpu(xp.tune); + wp->balance = xp.balance; + memcpy(wp->envelope_rate, xp.envelope_rate, 6); + memcpy(wp->envelope_offset, xp.envelope_offset, 6); + wp->tremolo_sweep = xp.tremolo_sweep; + wp->tremolo_rate = xp.tremolo_rate; + wp->tremolo_depth = xp.tremolo_depth; + wp->vibrato_sweep = xp.vibrato_sweep; + wp->vibrato_rate = xp.vibrato_rate; + wp->vibrato_depth = xp.vibrato_depth; + wp->scale_frequency = le16_to_cpu(xp.scale_frequency); + wp->scale_factor = le16_to_cpu(xp.scale_factor); + real_size = snd_seq_gf1_size(wp->size, wp->format); + if ((long)real_size > *len) { + kfree(wp); + return -ENOMEM; + } + if (ops->put_sample) { + err = ops->put_sample(ops->private_data, wp, + *data, real_size, atomic); + if (err < 0) { + kfree(wp); + return err; + } + } + *data += real_size; + *len -= real_size; + prev = ip->wave; + if (prev) { + while (prev->next) prev = prev->next; + prev->next = wp; + } else { + ip->wave = wp; + } + return 0; +} + +static void snd_seq_gf1_wave_free(snd_gf1_ops_t *ops, + gf1_wave_t *wave, + int atomic) +{ + if (ops->remove_sample) + ops->remove_sample(ops->private_data, wave, atomic); + kfree(wave); +} + +static void snd_seq_gf1_instr_free(snd_gf1_ops_t *ops, + gf1_instrument_t *ip, + int atomic) +{ + gf1_wave_t *wave; + + while ((wave = ip->wave) != NULL) { + ip->wave = wave->next; + snd_seq_gf1_wave_free(ops, wave, atomic); + } +} + +static int snd_seq_gf1_put(void *private_data, snd_seq_kinstr_t *instr, + char __user *instr_data, long len, int atomic, + int cmd) +{ + snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data; + gf1_instrument_t *ip; + gf1_xinstrument_t ix; + int err, gfp_mask; + + if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE) + return -EINVAL; + gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL; + /* copy instrument data */ + if (len < (long)sizeof(ix)) + return -EINVAL; + if (copy_from_user(&ix, instr_data, sizeof(ix))) + return -EFAULT; + if (ix.stype != GF1_STRU_INSTR) + return -EINVAL; + instr_data += sizeof(ix); + len -= sizeof(ix); + ip = (gf1_instrument_t *)KINSTR_DATA(instr); + ip->exclusion = le16_to_cpu(ix.exclusion); + ip->exclusion_group = le16_to_cpu(ix.exclusion_group); + ip->effect1 = ix.effect1; + ip->effect1_depth = ix.effect1_depth; + ip->effect2 = ix.effect2; + ip->effect2_depth = ix.effect2_depth; + /* copy layers */ + while (len > (long)sizeof(__u32)) { + __u32 stype; + + if (copy_from_user(&stype, instr_data, sizeof(stype))) + return -EFAULT; + if (stype != GF1_STRU_WAVE) { + snd_seq_gf1_instr_free(ops, ip, atomic); + return -EINVAL; + } + err = snd_seq_gf1_copy_wave_from_stream(ops, + ip, + &instr_data, + &len, + atomic); + if (err < 0) { + snd_seq_gf1_instr_free(ops, ip, atomic); + return err; + } + } + return 0; +} + +static int snd_seq_gf1_copy_wave_to_stream(snd_gf1_ops_t *ops, + gf1_instrument_t *ip, + char __user **data, + long *len, + int atomic) +{ + gf1_wave_t *wp; + gf1_xwave_t xp; + int err; + unsigned int real_size; + + for (wp = ip->wave; wp; wp = wp->next) { + if (*len < (long)sizeof(xp)) + return -ENOMEM; + memset(&xp, 0, sizeof(xp)); + xp.stype = GF1_STRU_WAVE; + xp.share_id[0] = cpu_to_le32(wp->share_id[0]); + xp.share_id[1] = cpu_to_le32(wp->share_id[1]); + xp.share_id[2] = cpu_to_le32(wp->share_id[2]); + xp.share_id[3] = cpu_to_le32(wp->share_id[3]); + xp.format = cpu_to_le32(wp->format); + xp.size = cpu_to_le32(wp->size); + xp.start = cpu_to_le32(wp->start); + xp.loop_start = cpu_to_le32(wp->loop_start); + xp.loop_end = cpu_to_le32(wp->loop_end); + xp.loop_repeat = cpu_to_le32(wp->loop_repeat); + xp.flags = wp->flags; + xp.sample_rate = cpu_to_le32(wp->sample_rate); + xp.low_frequency = cpu_to_le32(wp->low_frequency); + xp.high_frequency = cpu_to_le32(wp->high_frequency); + xp.root_frequency = cpu_to_le32(wp->root_frequency); + xp.tune = cpu_to_le16(wp->tune); + xp.balance = wp->balance; + memcpy(xp.envelope_rate, wp->envelope_rate, 6); + memcpy(xp.envelope_offset, wp->envelope_offset, 6); + xp.tremolo_sweep = wp->tremolo_sweep; + xp.tremolo_rate = wp->tremolo_rate; + xp.tremolo_depth = wp->tremolo_depth; + xp.vibrato_sweep = wp->vibrato_sweep; + xp.vibrato_rate = wp->vibrato_rate; + xp.vibrato_depth = wp->vibrato_depth; + xp.scale_frequency = cpu_to_le16(wp->scale_frequency); + xp.scale_factor = cpu_to_le16(wp->scale_factor); + if (copy_to_user(*data, &xp, sizeof(xp))) + return -EFAULT; + *data += sizeof(xp); + *len -= sizeof(xp); + real_size = snd_seq_gf1_size(wp->size, wp->format); + if (*len < (long)real_size) + return -ENOMEM; + if (ops->get_sample) { + err = ops->get_sample(ops->private_data, wp, + *data, real_size, atomic); + if (err < 0) + return err; + } + *data += wp->size; + *len -= wp->size; + } + return 0; +} + +static int snd_seq_gf1_get(void *private_data, snd_seq_kinstr_t *instr, + char __user *instr_data, long len, int atomic, + int cmd) +{ + snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data; + gf1_instrument_t *ip; + gf1_xinstrument_t ix; + + if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL) + return -EINVAL; + if (len < (long)sizeof(ix)) + return -ENOMEM; + memset(&ix, 0, sizeof(ix)); + ip = (gf1_instrument_t *)KINSTR_DATA(instr); + ix.stype = GF1_STRU_INSTR; + ix.exclusion = cpu_to_le16(ip->exclusion); + ix.exclusion_group = cpu_to_le16(ip->exclusion_group); + ix.effect1 = cpu_to_le16(ip->effect1); + ix.effect1_depth = cpu_to_le16(ip->effect1_depth); + ix.effect2 = ip->effect2; + ix.effect2_depth = ip->effect2_depth; + if (copy_to_user(instr_data, &ix, sizeof(ix))) + return -EFAULT; + instr_data += sizeof(ix); + len -= sizeof(ix); + return snd_seq_gf1_copy_wave_to_stream(ops, + ip, + &instr_data, + &len, + atomic); +} + +static int snd_seq_gf1_get_size(void *private_data, snd_seq_kinstr_t *instr, + long *size) +{ + long result; + gf1_instrument_t *ip; + gf1_wave_t *wp; + + *size = 0; + ip = (gf1_instrument_t *)KINSTR_DATA(instr); + result = sizeof(gf1_xinstrument_t); + for (wp = ip->wave; wp; wp = wp->next) { + result += sizeof(gf1_xwave_t); + result += wp->size; + } + *size = result; + return 0; +} + +static int snd_seq_gf1_remove(void *private_data, + snd_seq_kinstr_t *instr, + int atomic) +{ + snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data; + gf1_instrument_t *ip; + + ip = (gf1_instrument_t *)KINSTR_DATA(instr); + snd_seq_gf1_instr_free(ops, ip, atomic); + return 0; +} + +static void snd_seq_gf1_notify(void *private_data, + snd_seq_kinstr_t *instr, + int what) +{ + snd_gf1_ops_t *ops = (snd_gf1_ops_t *)private_data; + + if (ops->notify) + ops->notify(ops->private_data, instr, what); +} + +int snd_seq_gf1_init(snd_gf1_ops_t *ops, + void *private_data, + snd_seq_kinstr_ops_t *next) +{ + memset(ops, 0, sizeof(*ops)); + ops->private_data = private_data; + ops->kops.private_data = ops; + ops->kops.add_len = sizeof(gf1_instrument_t); + ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_GUS_PATCH; + ops->kops.put = snd_seq_gf1_put; + ops->kops.get = snd_seq_gf1_get; + ops->kops.get_size = snd_seq_gf1_get_size; + ops->kops.remove = snd_seq_gf1_remove; + ops->kops.notify = snd_seq_gf1_notify; + ops->kops.next = next; + return 0; +} + +/* + * Init part + */ + +static int __init alsa_ainstr_gf1_init(void) +{ + return 0; +} + +static void __exit alsa_ainstr_gf1_exit(void) +{ +} + +module_init(alsa_ainstr_gf1_init) +module_exit(alsa_ainstr_gf1_exit) + +EXPORT_SYMBOL(snd_seq_gf1_init); diff --git a/sound/core/seq/instr/ainstr_iw.c b/sound/core/seq/instr/ainstr_iw.c new file mode 100644 index 000000000000..39ff72b2aab3 --- /dev/null +++ b/sound/core/seq/instr/ainstr_iw.c @@ -0,0 +1,622 @@ +/* + * IWFFFF - AMD InterWave (tm) - Instrument routines + * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/ainstr_iw.h> +#include <sound/initval.h> +#include <asm/uaccess.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture IWFFFF support."); +MODULE_LICENSE("GPL"); + +static unsigned int snd_seq_iwffff_size(unsigned int size, unsigned int format) +{ + unsigned int result = size; + + if (format & IWFFFF_WAVE_16BIT) + result <<= 1; + if (format & IWFFFF_WAVE_STEREO) + result <<= 1; + return result; +} + +static void snd_seq_iwffff_copy_lfo_from_stream(iwffff_lfo_t *fp, + iwffff_xlfo_t *fx) +{ + fp->freq = le16_to_cpu(fx->freq); + fp->depth = le16_to_cpu(fx->depth); + fp->sweep = le16_to_cpu(fx->sweep); + fp->shape = fx->shape; + fp->delay = fx->delay; +} + +static int snd_seq_iwffff_copy_env_from_stream(__u32 req_stype, + iwffff_layer_t *lp, + iwffff_env_t *ep, + iwffff_xenv_t *ex, + char __user **data, + long *len, + int gfp_mask) +{ + __u32 stype; + iwffff_env_record_t *rp, *rp_last; + iwffff_xenv_record_t rx; + iwffff_env_point_t *pp; + iwffff_xenv_point_t px; + int points_size, idx; + + ep->flags = ex->flags; + ep->mode = ex->mode; + ep->index = ex->index; + rp_last = NULL; + while (1) { + if (*len < (long)sizeof(__u32)) + return -EINVAL; + if (copy_from_user(&stype, *data, sizeof(stype))) + return -EFAULT; + if (stype == IWFFFF_STRU_WAVE) + return 0; + if (req_stype != stype) { + if (stype == IWFFFF_STRU_ENV_RECP || + stype == IWFFFF_STRU_ENV_RECV) + return 0; + } + if (*len < (long)sizeof(rx)) + return -EINVAL; + if (copy_from_user(&rx, *data, sizeof(rx))) + return -EFAULT; + *data += sizeof(rx); + *len -= sizeof(rx); + points_size = (le16_to_cpu(rx.nattack) + le16_to_cpu(rx.nrelease)) * 2 * sizeof(__u16); + if (points_size > *len) + return -EINVAL; + rp = kcalloc(1, sizeof(*rp) + points_size, gfp_mask); + if (rp == NULL) + return -ENOMEM; + rp->nattack = le16_to_cpu(rx.nattack); + rp->nrelease = le16_to_cpu(rx.nrelease); + rp->sustain_offset = le16_to_cpu(rx.sustain_offset); + rp->sustain_rate = le16_to_cpu(rx.sustain_rate); + rp->release_rate = le16_to_cpu(rx.release_rate); + rp->hirange = rx.hirange; + pp = (iwffff_env_point_t *)(rp + 1); + for (idx = 0; idx < rp->nattack + rp->nrelease; idx++) { + if (copy_from_user(&px, *data, sizeof(px))) + return -EFAULT; + *data += sizeof(px); + *len -= sizeof(px); + pp->offset = le16_to_cpu(px.offset); + pp->rate = le16_to_cpu(px.rate); + } + if (ep->record == NULL) { + ep->record = rp; + } else { + rp_last = rp; + } + rp_last = rp; + } + return 0; +} + +static int snd_seq_iwffff_copy_wave_from_stream(snd_iwffff_ops_t *ops, + iwffff_layer_t *lp, + char __user **data, + long *len, + int atomic) +{ + iwffff_wave_t *wp, *prev; + iwffff_xwave_t xp; + int err, gfp_mask; + unsigned int real_size; + + gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL; + if (*len < (long)sizeof(xp)) + return -EINVAL; + if (copy_from_user(&xp, *data, sizeof(xp))) + return -EFAULT; + *data += sizeof(xp); + *len -= sizeof(xp); + wp = kcalloc(1, sizeof(*wp), gfp_mask); + if (wp == NULL) + return -ENOMEM; + wp->share_id[0] = le32_to_cpu(xp.share_id[0]); + wp->share_id[1] = le32_to_cpu(xp.share_id[1]); + wp->share_id[2] = le32_to_cpu(xp.share_id[2]); + wp->share_id[3] = le32_to_cpu(xp.share_id[3]); + wp->format = le32_to_cpu(xp.format); + wp->address.memory = le32_to_cpu(xp.offset); + wp->size = le32_to_cpu(xp.size); + wp->start = le32_to_cpu(xp.start); + wp->loop_start = le32_to_cpu(xp.loop_start); + wp->loop_end = le32_to_cpu(xp.loop_end); + wp->loop_repeat = le16_to_cpu(xp.loop_repeat); + wp->sample_ratio = le32_to_cpu(xp.sample_ratio); + wp->attenuation = xp.attenuation; + wp->low_note = xp.low_note; + wp->high_note = xp.high_note; + real_size = snd_seq_iwffff_size(wp->size, wp->format); + if (!(wp->format & IWFFFF_WAVE_ROM)) { + if ((long)real_size > *len) { + kfree(wp); + return -ENOMEM; + } + } + if (ops->put_sample) { + err = ops->put_sample(ops->private_data, wp, + *data, real_size, atomic); + if (err < 0) { + kfree(wp); + return err; + } + } + if (!(wp->format & IWFFFF_WAVE_ROM)) { + *data += real_size; + *len -= real_size; + } + prev = lp->wave; + if (prev) { + while (prev->next) prev = prev->next; + prev->next = wp; + } else { + lp->wave = wp; + } + return 0; +} + +static void snd_seq_iwffff_env_free(snd_iwffff_ops_t *ops, + iwffff_env_t *env, + int atomic) +{ + iwffff_env_record_t *rec; + + while ((rec = env->record) != NULL) { + env->record = rec->next; + kfree(rec); + } +} + +static void snd_seq_iwffff_wave_free(snd_iwffff_ops_t *ops, + iwffff_wave_t *wave, + int atomic) +{ + if (ops->remove_sample) + ops->remove_sample(ops->private_data, wave, atomic); + kfree(wave); +} + +static void snd_seq_iwffff_instr_free(snd_iwffff_ops_t *ops, + iwffff_instrument_t *ip, + int atomic) +{ + iwffff_layer_t *layer; + iwffff_wave_t *wave; + + while ((layer = ip->layer) != NULL) { + ip->layer = layer->next; + snd_seq_iwffff_env_free(ops, &layer->penv, atomic); + snd_seq_iwffff_env_free(ops, &layer->venv, atomic); + while ((wave = layer->wave) != NULL) { + layer->wave = wave->next; + snd_seq_iwffff_wave_free(ops, wave, atomic); + } + kfree(layer); + } +} + +static int snd_seq_iwffff_put(void *private_data, snd_seq_kinstr_t *instr, + char __user *instr_data, long len, int atomic, + int cmd) +{ + snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data; + iwffff_instrument_t *ip; + iwffff_xinstrument_t ix; + iwffff_layer_t *lp, *prev_lp; + iwffff_xlayer_t lx; + int err, gfp_mask; + + if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE) + return -EINVAL; + gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL; + /* copy instrument data */ + if (len < (long)sizeof(ix)) + return -EINVAL; + if (copy_from_user(&ix, instr_data, sizeof(ix))) + return -EFAULT; + if (ix.stype != IWFFFF_STRU_INSTR) + return -EINVAL; + instr_data += sizeof(ix); + len -= sizeof(ix); + ip = (iwffff_instrument_t *)KINSTR_DATA(instr); + ip->exclusion = le16_to_cpu(ix.exclusion); + ip->layer_type = le16_to_cpu(ix.layer_type); + ip->exclusion_group = le16_to_cpu(ix.exclusion_group); + ip->effect1 = ix.effect1; + ip->effect1_depth = ix.effect1_depth; + ip->effect2 = ix.effect2; + ip->effect2_depth = ix.effect2_depth; + /* copy layers */ + prev_lp = NULL; + while (len > 0) { + if (len < (long)sizeof(iwffff_xlayer_t)) { + snd_seq_iwffff_instr_free(ops, ip, atomic); + return -EINVAL; + } + if (copy_from_user(&lx, instr_data, sizeof(lx))) + return -EFAULT; + instr_data += sizeof(lx); + len -= sizeof(lx); + if (lx.stype != IWFFFF_STRU_LAYER) { + snd_seq_iwffff_instr_free(ops, ip, atomic); + return -EINVAL; + } + lp = kcalloc(1, sizeof(*lp), gfp_mask); + if (lp == NULL) { + snd_seq_iwffff_instr_free(ops, ip, atomic); + return -ENOMEM; + } + if (prev_lp) { + prev_lp->next = lp; + } else { + ip->layer = lp; + } + prev_lp = lp; + lp->flags = lx.flags; + lp->velocity_mode = lx.velocity_mode; + lp->layer_event = lx.layer_event; + lp->low_range = lx.low_range; + lp->high_range = lx.high_range; + lp->pan = lx.pan; + lp->pan_freq_scale = lx.pan_freq_scale; + lp->attenuation = lx.attenuation; + snd_seq_iwffff_copy_lfo_from_stream(&lp->tremolo, &lx.tremolo); + snd_seq_iwffff_copy_lfo_from_stream(&lp->vibrato, &lx.vibrato); + lp->freq_scale = le16_to_cpu(lx.freq_scale); + lp->freq_center = lx.freq_center; + err = snd_seq_iwffff_copy_env_from_stream(IWFFFF_STRU_ENV_RECP, + lp, + &lp->penv, &lx.penv, + &instr_data, &len, + gfp_mask); + if (err < 0) { + snd_seq_iwffff_instr_free(ops, ip, atomic); + return err; + } + err = snd_seq_iwffff_copy_env_from_stream(IWFFFF_STRU_ENV_RECV, + lp, + &lp->venv, &lx.venv, + &instr_data, &len, + gfp_mask); + if (err < 0) { + snd_seq_iwffff_instr_free(ops, ip, atomic); + return err; + } + while (len > (long)sizeof(__u32)) { + __u32 stype; + + if (copy_from_user(&stype, instr_data, sizeof(stype))) + return -EFAULT; + if (stype != IWFFFF_STRU_WAVE) + break; + err = snd_seq_iwffff_copy_wave_from_stream(ops, + lp, + &instr_data, + &len, + atomic); + if (err < 0) { + snd_seq_iwffff_instr_free(ops, ip, atomic); + return err; + } + } + } + return 0; +} + +static void snd_seq_iwffff_copy_lfo_to_stream(iwffff_xlfo_t *fx, + iwffff_lfo_t *fp) +{ + fx->freq = cpu_to_le16(fp->freq); + fx->depth = cpu_to_le16(fp->depth); + fx->sweep = cpu_to_le16(fp->sweep); + fp->shape = fx->shape; + fp->delay = fx->delay; +} + +static int snd_seq_iwffff_copy_env_to_stream(__u32 req_stype, + iwffff_layer_t *lp, + iwffff_xenv_t *ex, + iwffff_env_t *ep, + char __user **data, + long *len) +{ + iwffff_env_record_t *rp; + iwffff_xenv_record_t rx; + iwffff_env_point_t *pp; + iwffff_xenv_point_t px; + int points_size, idx; + + ex->flags = ep->flags; + ex->mode = ep->mode; + ex->index = ep->index; + for (rp = ep->record; rp; rp = rp->next) { + if (*len < (long)sizeof(rx)) + return -ENOMEM; + memset(&rx, 0, sizeof(rx)); + rx.stype = req_stype; + rx.nattack = cpu_to_le16(rp->nattack); + rx.nrelease = cpu_to_le16(rp->nrelease); + rx.sustain_offset = cpu_to_le16(rp->sustain_offset); + rx.sustain_rate = cpu_to_le16(rp->sustain_rate); + rx.release_rate = cpu_to_le16(rp->release_rate); + rx.hirange = cpu_to_le16(rp->hirange); + if (copy_to_user(*data, &rx, sizeof(rx))) + return -EFAULT; + *data += sizeof(rx); + *len -= sizeof(rx); + points_size = (rp->nattack + rp->nrelease) * 2 * sizeof(__u16); + if (*len < points_size) + return -ENOMEM; + pp = (iwffff_env_point_t *)(rp + 1); + for (idx = 0; idx < rp->nattack + rp->nrelease; idx++) { + px.offset = cpu_to_le16(pp->offset); + px.rate = cpu_to_le16(pp->rate); + if (copy_to_user(*data, &px, sizeof(px))) + return -EFAULT; + *data += sizeof(px); + *len -= sizeof(px); + } + } + return 0; +} + +static int snd_seq_iwffff_copy_wave_to_stream(snd_iwffff_ops_t *ops, + iwffff_layer_t *lp, + char __user **data, + long *len, + int atomic) +{ + iwffff_wave_t *wp; + iwffff_xwave_t xp; + int err; + unsigned int real_size; + + for (wp = lp->wave; wp; wp = wp->next) { + if (*len < (long)sizeof(xp)) + return -ENOMEM; + memset(&xp, 0, sizeof(xp)); + xp.stype = IWFFFF_STRU_WAVE; + xp.share_id[0] = cpu_to_le32(wp->share_id[0]); + xp.share_id[1] = cpu_to_le32(wp->share_id[1]); + xp.share_id[2] = cpu_to_le32(wp->share_id[2]); + xp.share_id[3] = cpu_to_le32(wp->share_id[3]); + xp.format = cpu_to_le32(wp->format); + if (wp->format & IWFFFF_WAVE_ROM) + xp.offset = cpu_to_le32(wp->address.memory); + xp.size = cpu_to_le32(wp->size); + xp.start = cpu_to_le32(wp->start); + xp.loop_start = cpu_to_le32(wp->loop_start); + xp.loop_end = cpu_to_le32(wp->loop_end); + xp.loop_repeat = cpu_to_le32(wp->loop_repeat); + xp.sample_ratio = cpu_to_le32(wp->sample_ratio); + xp.attenuation = wp->attenuation; + xp.low_note = wp->low_note; + xp.high_note = wp->high_note; + if (copy_to_user(*data, &xp, sizeof(xp))) + return -EFAULT; + *data += sizeof(xp); + *len -= sizeof(xp); + real_size = snd_seq_iwffff_size(wp->size, wp->format); + if (!(wp->format & IWFFFF_WAVE_ROM)) { + if (*len < (long)real_size) + return -ENOMEM; + } + if (ops->get_sample) { + err = ops->get_sample(ops->private_data, wp, + *data, real_size, atomic); + if (err < 0) + return err; + } + if (!(wp->format & IWFFFF_WAVE_ROM)) { + *data += real_size; + *len -= real_size; + } + } + return 0; +} + +static int snd_seq_iwffff_get(void *private_data, snd_seq_kinstr_t *instr, + char __user *instr_data, long len, int atomic, int cmd) +{ + snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data; + iwffff_instrument_t *ip; + iwffff_xinstrument_t ix; + iwffff_layer_t *lp; + iwffff_xlayer_t lx; + char __user *layer_instr_data; + int err; + + if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL) + return -EINVAL; + if (len < (long)sizeof(ix)) + return -ENOMEM; + memset(&ix, 0, sizeof(ix)); + ip = (iwffff_instrument_t *)KINSTR_DATA(instr); + ix.stype = IWFFFF_STRU_INSTR; + ix.exclusion = cpu_to_le16(ip->exclusion); + ix.layer_type = cpu_to_le16(ip->layer_type); + ix.exclusion_group = cpu_to_le16(ip->exclusion_group); + ix.effect1 = cpu_to_le16(ip->effect1); + ix.effect1_depth = cpu_to_le16(ip->effect1_depth); + ix.effect2 = ip->effect2; + ix.effect2_depth = ip->effect2_depth; + if (copy_to_user(instr_data, &ix, sizeof(ix))) + return -EFAULT; + instr_data += sizeof(ix); + len -= sizeof(ix); + for (lp = ip->layer; lp; lp = lp->next) { + if (len < (long)sizeof(lx)) + return -ENOMEM; + memset(&lx, 0, sizeof(lx)); + lx.stype = IWFFFF_STRU_LAYER; + lx.flags = lp->flags; + lx.velocity_mode = lp->velocity_mode; + lx.layer_event = lp->layer_event; + lx.low_range = lp->low_range; + lx.high_range = lp->high_range; + lx.pan = lp->pan; + lx.pan_freq_scale = lp->pan_freq_scale; + lx.attenuation = lp->attenuation; + snd_seq_iwffff_copy_lfo_to_stream(&lx.tremolo, &lp->tremolo); + snd_seq_iwffff_copy_lfo_to_stream(&lx.vibrato, &lp->vibrato); + layer_instr_data = instr_data; + instr_data += sizeof(lx); + len -= sizeof(lx); + err = snd_seq_iwffff_copy_env_to_stream(IWFFFF_STRU_ENV_RECP, + lp, + &lx.penv, &lp->penv, + &instr_data, &len); + if (err < 0) + return err; + err = snd_seq_iwffff_copy_env_to_stream(IWFFFF_STRU_ENV_RECV, + lp, + &lx.venv, &lp->venv, + &instr_data, &len); + if (err < 0) + return err; + /* layer structure updating is now finished */ + if (copy_to_user(layer_instr_data, &lx, sizeof(lx))) + return -EFAULT; + err = snd_seq_iwffff_copy_wave_to_stream(ops, + lp, + &instr_data, + &len, + atomic); + if (err < 0) + return err; + } + return 0; +} + +static long snd_seq_iwffff_env_size_in_stream(iwffff_env_t *ep) +{ + long result = 0; + iwffff_env_record_t *rp; + + for (rp = ep->record; rp; rp = rp->next) { + result += sizeof(iwffff_xenv_record_t); + result += (rp->nattack + rp->nrelease) * 2 * sizeof(__u16); + } + return 0; +} + +static long snd_seq_iwffff_wave_size_in_stream(iwffff_layer_t *lp) +{ + long result = 0; + iwffff_wave_t *wp; + + for (wp = lp->wave; wp; wp = wp->next) { + result += sizeof(iwffff_xwave_t); + if (!(wp->format & IWFFFF_WAVE_ROM)) + result += wp->size; + } + return result; +} + +static int snd_seq_iwffff_get_size(void *private_data, snd_seq_kinstr_t *instr, + long *size) +{ + long result; + iwffff_instrument_t *ip; + iwffff_layer_t *lp; + + *size = 0; + ip = (iwffff_instrument_t *)KINSTR_DATA(instr); + result = sizeof(iwffff_xinstrument_t); + for (lp = ip->layer; lp; lp = lp->next) { + result += sizeof(iwffff_xlayer_t); + result += snd_seq_iwffff_env_size_in_stream(&lp->penv); + result += snd_seq_iwffff_env_size_in_stream(&lp->venv); + result += snd_seq_iwffff_wave_size_in_stream(lp); + } + *size = result; + return 0; +} + +static int snd_seq_iwffff_remove(void *private_data, + snd_seq_kinstr_t *instr, + int atomic) +{ + snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data; + iwffff_instrument_t *ip; + + ip = (iwffff_instrument_t *)KINSTR_DATA(instr); + snd_seq_iwffff_instr_free(ops, ip, atomic); + return 0; +} + +static void snd_seq_iwffff_notify(void *private_data, + snd_seq_kinstr_t *instr, + int what) +{ + snd_iwffff_ops_t *ops = (snd_iwffff_ops_t *)private_data; + + if (ops->notify) + ops->notify(ops->private_data, instr, what); +} + +int snd_seq_iwffff_init(snd_iwffff_ops_t *ops, + void *private_data, + snd_seq_kinstr_ops_t *next) +{ + memset(ops, 0, sizeof(*ops)); + ops->private_data = private_data; + ops->kops.private_data = ops; + ops->kops.add_len = sizeof(iwffff_instrument_t); + ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_INTERWAVE; + ops->kops.put = snd_seq_iwffff_put; + ops->kops.get = snd_seq_iwffff_get; + ops->kops.get_size = snd_seq_iwffff_get_size; + ops->kops.remove = snd_seq_iwffff_remove; + ops->kops.notify = snd_seq_iwffff_notify; + ops->kops.next = next; + return 0; +} + +/* + * Init part + */ + +static int __init alsa_ainstr_iw_init(void) +{ + return 0; +} + +static void __exit alsa_ainstr_iw_exit(void) +{ +} + +module_init(alsa_ainstr_iw_init) +module_exit(alsa_ainstr_iw_exit) + +EXPORT_SYMBOL(snd_seq_iwffff_init); diff --git a/sound/core/seq/instr/ainstr_simple.c b/sound/core/seq/instr/ainstr_simple.c new file mode 100644 index 000000000000..6183d2151034 --- /dev/null +++ b/sound/core/seq/instr/ainstr_simple.c @@ -0,0 +1,215 @@ +/* + * Simple (MOD player) - Instrument routines + * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/ainstr_simple.h> +#include <sound/initval.h> +#include <asm/uaccess.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture Simple Instrument support."); +MODULE_LICENSE("GPL"); + +static unsigned int snd_seq_simple_size(unsigned int size, unsigned int format) +{ + unsigned int result = size; + + if (format & SIMPLE_WAVE_16BIT) + result <<= 1; + if (format & SIMPLE_WAVE_STEREO) + result <<= 1; + return result; +} + +static void snd_seq_simple_instr_free(snd_simple_ops_t *ops, + simple_instrument_t *ip, + int atomic) +{ + if (ops->remove_sample) + ops->remove_sample(ops->private_data, ip, atomic); +} + +static int snd_seq_simple_put(void *private_data, snd_seq_kinstr_t *instr, + char __user *instr_data, long len, + int atomic, int cmd) +{ + snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data; + simple_instrument_t *ip; + simple_xinstrument_t ix; + int err, gfp_mask; + unsigned int real_size; + + if (cmd != SNDRV_SEQ_INSTR_PUT_CMD_CREATE) + return -EINVAL; + gfp_mask = atomic ? GFP_ATOMIC : GFP_KERNEL; + /* copy instrument data */ + if (len < (long)sizeof(ix)) + return -EINVAL; + if (copy_from_user(&ix, instr_data, sizeof(ix))) + return -EFAULT; + if (ix.stype != SIMPLE_STRU_INSTR) + return -EINVAL; + instr_data += sizeof(ix); + len -= sizeof(ix); + ip = (simple_instrument_t *)KINSTR_DATA(instr); + ip->share_id[0] = le32_to_cpu(ix.share_id[0]); + ip->share_id[1] = le32_to_cpu(ix.share_id[1]); + ip->share_id[2] = le32_to_cpu(ix.share_id[2]); + ip->share_id[3] = le32_to_cpu(ix.share_id[3]); + ip->format = le32_to_cpu(ix.format); + ip->size = le32_to_cpu(ix.size); + ip->start = le32_to_cpu(ix.start); + ip->loop_start = le32_to_cpu(ix.loop_start); + ip->loop_end = le32_to_cpu(ix.loop_end); + ip->loop_repeat = le16_to_cpu(ix.loop_repeat); + ip->effect1 = ix.effect1; + ip->effect1_depth = ix.effect1_depth; + ip->effect2 = ix.effect2; + ip->effect2_depth = ix.effect2_depth; + real_size = snd_seq_simple_size(ip->size, ip->format); + if (len < (long)real_size) + return -EINVAL; + if (ops->put_sample) { + err = ops->put_sample(ops->private_data, ip, + instr_data, real_size, atomic); + if (err < 0) + return err; + } + return 0; +} + +static int snd_seq_simple_get(void *private_data, snd_seq_kinstr_t *instr, + char __user *instr_data, long len, + int atomic, int cmd) +{ + snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data; + simple_instrument_t *ip; + simple_xinstrument_t ix; + int err; + unsigned int real_size; + + if (cmd != SNDRV_SEQ_INSTR_GET_CMD_FULL) + return -EINVAL; + if (len < (long)sizeof(ix)) + return -ENOMEM; + memset(&ix, 0, sizeof(ix)); + ip = (simple_instrument_t *)KINSTR_DATA(instr); + ix.stype = SIMPLE_STRU_INSTR; + ix.share_id[0] = cpu_to_le32(ip->share_id[0]); + ix.share_id[1] = cpu_to_le32(ip->share_id[1]); + ix.share_id[2] = cpu_to_le32(ip->share_id[2]); + ix.share_id[3] = cpu_to_le32(ip->share_id[3]); + ix.format = cpu_to_le32(ip->format); + ix.size = cpu_to_le32(ip->size); + ix.start = cpu_to_le32(ip->start); + ix.loop_start = cpu_to_le32(ip->loop_start); + ix.loop_end = cpu_to_le32(ip->loop_end); + ix.loop_repeat = cpu_to_le32(ip->loop_repeat); + ix.effect1 = cpu_to_le16(ip->effect1); + ix.effect1_depth = cpu_to_le16(ip->effect1_depth); + ix.effect2 = ip->effect2; + ix.effect2_depth = ip->effect2_depth; + if (copy_to_user(instr_data, &ix, sizeof(ix))) + return -EFAULT; + instr_data += sizeof(ix); + len -= sizeof(ix); + real_size = snd_seq_simple_size(ip->size, ip->format); + if (len < (long)real_size) + return -ENOMEM; + if (ops->get_sample) { + err = ops->get_sample(ops->private_data, ip, + instr_data, real_size, atomic); + if (err < 0) + return err; + } + return 0; +} + +static int snd_seq_simple_get_size(void *private_data, snd_seq_kinstr_t *instr, + long *size) +{ + simple_instrument_t *ip; + + ip = (simple_instrument_t *)KINSTR_DATA(instr); + *size = sizeof(simple_xinstrument_t) + snd_seq_simple_size(ip->size, ip->format); + return 0; +} + +static int snd_seq_simple_remove(void *private_data, + snd_seq_kinstr_t *instr, + int atomic) +{ + snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data; + simple_instrument_t *ip; + + ip = (simple_instrument_t *)KINSTR_DATA(instr); + snd_seq_simple_instr_free(ops, ip, atomic); + return 0; +} + +static void snd_seq_simple_notify(void *private_data, + snd_seq_kinstr_t *instr, + int what) +{ + snd_simple_ops_t *ops = (snd_simple_ops_t *)private_data; + + if (ops->notify) + ops->notify(ops->private_data, instr, what); +} + +int snd_seq_simple_init(snd_simple_ops_t *ops, + void *private_data, + snd_seq_kinstr_ops_t *next) +{ + memset(ops, 0, sizeof(*ops)); + ops->private_data = private_data; + ops->kops.private_data = ops; + ops->kops.add_len = sizeof(simple_instrument_t); + ops->kops.instr_type = SNDRV_SEQ_INSTR_ID_SIMPLE; + ops->kops.put = snd_seq_simple_put; + ops->kops.get = snd_seq_simple_get; + ops->kops.get_size = snd_seq_simple_get_size; + ops->kops.remove = snd_seq_simple_remove; + ops->kops.notify = snd_seq_simple_notify; + ops->kops.next = next; + return 0; +} + +/* + * Init part + */ + +static int __init alsa_ainstr_simple_init(void) +{ + return 0; +} + +static void __exit alsa_ainstr_simple_exit(void) +{ +} + +module_init(alsa_ainstr_simple_init) +module_exit(alsa_ainstr_simple_exit) + +EXPORT_SYMBOL(snd_seq_simple_init); diff --git a/sound/core/seq/oss/Makefile b/sound/core/seq/oss/Makefile new file mode 100644 index 000000000000..a37ddedf7107 --- /dev/null +++ b/sound/core/seq/oss/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> +# + +snd-seq-oss-objs := seq_oss.o seq_oss_init.o seq_oss_timer.o seq_oss_ioctl.o \ + seq_oss_event.o seq_oss_rw.o seq_oss_synth.o \ + seq_oss_midi.o seq_oss_readq.o seq_oss_writeq.o + +obj-$(CONFIG_SND_SEQUENCER) += snd-seq-oss.o diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c new file mode 100644 index 000000000000..4c0558c0a8b4 --- /dev/null +++ b/sound/core/seq/oss/seq_oss.c @@ -0,0 +1,317 @@ +/* + * OSS compatible sequencer driver + * + * registration of device and proc + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/initval.h> +#include "seq_oss_device.h" +#include "seq_oss_synth.h" + +/* + * module option + */ +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("OSS-compatible sequencer module"); +MODULE_LICENSE("GPL"); +/* Takashi says this is really only for sound-service-0-, but this is OK. */ +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_SEQUENCER); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MUSIC); + +#ifdef SNDRV_SEQ_OSS_DEBUG +module_param(seq_oss_debug, int, 0644); +MODULE_PARM_DESC(seq_oss_debug, "debug option"); +int seq_oss_debug = 0; +#endif + + +/* + * prototypes + */ +static int register_device(void); +static void unregister_device(void); +static int register_proc(void); +static void unregister_proc(void); + +static int odev_open(struct inode *inode, struct file *file); +static int odev_release(struct inode *inode, struct file *file); +static ssize_t odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset); +static ssize_t odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset); +static long odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +static unsigned int odev_poll(struct file *file, poll_table * wait); +#ifdef CONFIG_PROC_FS +static void info_read(snd_info_entry_t *entry, snd_info_buffer_t *buf); +#endif + + +/* + * module interface + */ + +static int __init alsa_seq_oss_init(void) +{ + int rc; + static snd_seq_dev_ops_t ops = { + snd_seq_oss_synth_register, + snd_seq_oss_synth_unregister, + }; + + snd_seq_autoload_lock(); + if ((rc = register_device()) < 0) + goto error; + if ((rc = register_proc()) < 0) { + unregister_device(); + goto error; + } + if ((rc = snd_seq_oss_create_client()) < 0) { + unregister_proc(); + unregister_device(); + goto error; + } + + if ((rc = snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OSS, &ops, + sizeof(snd_seq_oss_reg_t))) < 0) { + snd_seq_oss_delete_client(); + unregister_proc(); + unregister_device(); + goto error; + } + + /* success */ + snd_seq_oss_synth_init(); + + error: + snd_seq_autoload_unlock(); + return rc; +} + +static void __exit alsa_seq_oss_exit(void) +{ + snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OSS); + snd_seq_oss_delete_client(); + unregister_proc(); + unregister_device(); +} + +module_init(alsa_seq_oss_init) +module_exit(alsa_seq_oss_exit) + +/* + * ALSA minor device interface + */ + +static DECLARE_MUTEX(register_mutex); + +static int +odev_open(struct inode *inode, struct file *file) +{ + int level, rc; + + if (iminor(inode) == SNDRV_MINOR_OSS_MUSIC) + level = SNDRV_SEQ_OSS_MODE_MUSIC; + else + level = SNDRV_SEQ_OSS_MODE_SYNTH; + + down(®ister_mutex); + rc = snd_seq_oss_open(file, level); + up(®ister_mutex); + + return rc; +} + +static int +odev_release(struct inode *inode, struct file *file) +{ + seq_oss_devinfo_t *dp; + + if ((dp = file->private_data) == NULL) + return 0; + + snd_seq_oss_drain_write(dp); + + down(®ister_mutex); + snd_seq_oss_release(dp); + up(®ister_mutex); + + return 0; +} + +static ssize_t +odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + seq_oss_devinfo_t *dp; + dp = file->private_data; + snd_assert(dp != NULL, return -EIO); + return snd_seq_oss_read(dp, buf, count); +} + + +static ssize_t +odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ + seq_oss_devinfo_t *dp; + dp = file->private_data; + snd_assert(dp != NULL, return -EIO); + return snd_seq_oss_write(dp, buf, count, file); +} + +static long +odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + seq_oss_devinfo_t *dp; + dp = file->private_data; + snd_assert(dp != NULL, return -EIO); + return snd_seq_oss_ioctl(dp, cmd, arg); +} + +#ifdef CONFIG_COMPAT +#define odev_ioctl_compat odev_ioctl +#else +#define odev_ioctl_compat NULL +#endif + +static unsigned int +odev_poll(struct file *file, poll_table * wait) +{ + seq_oss_devinfo_t *dp; + dp = file->private_data; + snd_assert(dp != NULL, return 0); + return snd_seq_oss_poll(dp, file, wait); +} + +/* + * registration of sequencer minor device + */ + +static struct file_operations seq_oss_f_ops = +{ + .owner = THIS_MODULE, + .read = odev_read, + .write = odev_write, + .open = odev_open, + .release = odev_release, + .poll = odev_poll, + .unlocked_ioctl = odev_ioctl, + .compat_ioctl = odev_ioctl_compat, +}; + +static snd_minor_t seq_oss_reg = { + .comment = "sequencer", + .f_ops = &seq_oss_f_ops, +}; + +static int __init +register_device(void) +{ + int rc; + + down(®ister_mutex); + if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, + NULL, 0, + &seq_oss_reg, + SNDRV_SEQ_OSS_DEVNAME)) < 0) { + snd_printk(KERN_ERR "can't register device seq\n"); + up(®ister_mutex); + return rc; + } + if ((rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, + NULL, 0, + &seq_oss_reg, + SNDRV_SEQ_OSS_DEVNAME)) < 0) { + snd_printk(KERN_ERR "can't register device music\n"); + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0); + up(®ister_mutex); + return rc; + } + debug_printk(("device registered\n")); + up(®ister_mutex); + return 0; +} + +static void +unregister_device(void) +{ + down(®ister_mutex); + debug_printk(("device unregistered\n")); + if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, NULL, 0) < 0) + snd_printk(KERN_ERR "error unregister device music\n"); + if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0) < 0) + snd_printk(KERN_ERR "error unregister device seq\n"); + up(®ister_mutex); +} + +/* + * /proc interface + */ + +#ifdef CONFIG_PROC_FS + +static snd_info_entry_t *info_entry; + +static void +info_read(snd_info_entry_t *entry, snd_info_buffer_t *buf) +{ + down(®ister_mutex); + snd_iprintf(buf, "OSS sequencer emulation version %s\n", SNDRV_SEQ_OSS_VERSION_STR); + snd_seq_oss_system_info_read(buf); + snd_seq_oss_synth_info_read(buf); + snd_seq_oss_midi_info_read(buf); + up(®ister_mutex); +} + +#endif /* CONFIG_PROC_FS */ + +static int __init +register_proc(void) +{ +#ifdef CONFIG_PROC_FS + snd_info_entry_t *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, SNDRV_SEQ_OSS_PROCNAME, snd_seq_root); + if (entry == NULL) + return -ENOMEM; + + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = NULL; + entry->c.text.read_size = 1024; + entry->c.text.read = info_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + info_entry = entry; +#endif + return 0; +} + +static void +unregister_proc(void) +{ +#ifdef CONFIG_PROC_FS + if (info_entry) + snd_info_unregister(info_entry); + info_entry = NULL; +#endif +} diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h new file mode 100644 index 000000000000..da23c4db8dd5 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_device.h @@ -0,0 +1,198 @@ +/* + * OSS compatible sequencer driver + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_DEVICE_H +#define __SEQ_OSS_DEVICE_H + +#include <sound/driver.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <sound/core.h> +#include <sound/seq_oss.h> +#include <sound/rawmidi.h> +#include <sound/seq_kernel.h> +#include <sound/info.h> + +/* enable debug print */ +#define SNDRV_SEQ_OSS_DEBUG + +/* max. applications */ +#define SNDRV_SEQ_OSS_MAX_CLIENTS 16 +#define SNDRV_SEQ_OSS_MAX_SYNTH_DEVS 16 +#define SNDRV_SEQ_OSS_MAX_MIDI_DEVS 32 + +/* version */ +#define SNDRV_SEQ_OSS_MAJOR_VERSION 0 +#define SNDRV_SEQ_OSS_MINOR_VERSION 1 +#define SNDRV_SEQ_OSS_TINY_VERSION 8 +#define SNDRV_SEQ_OSS_VERSION_STR "0.1.8" + +/* device and proc interface name */ +#define SNDRV_SEQ_OSS_DEVNAME "seq_oss" +#define SNDRV_SEQ_OSS_PROCNAME "oss" + + +/* + * type definitions + */ + +typedef struct seq_oss_devinfo_t seq_oss_devinfo_t; +typedef struct seq_oss_writeq_t seq_oss_writeq_t; +typedef struct seq_oss_readq_t seq_oss_readq_t; +typedef struct seq_oss_timer_t seq_oss_timer_t; +typedef struct seq_oss_synthinfo_t seq_oss_synthinfo_t; +typedef struct seq_oss_synth_sysex_t seq_oss_synth_sysex_t; +typedef struct seq_oss_chinfo_t seq_oss_chinfo_t; +typedef unsigned int reltime_t; +typedef unsigned int abstime_t; +typedef union evrec_t evrec_t; + + +/* + * synthesizer channel information + */ +struct seq_oss_chinfo_t { + int note, vel; +}; + +/* + * synthesizer information + */ +struct seq_oss_synthinfo_t { + snd_seq_oss_arg_t arg; + seq_oss_chinfo_t *ch; + seq_oss_synth_sysex_t *sysex; + int nr_voices; + int opened; + int is_midi; + int midi_mapped; +}; + + +/* + * sequencer client information + */ + +struct seq_oss_devinfo_t { + + int index; /* application index */ + int cseq; /* sequencer client number */ + int port; /* sequencer port number */ + int queue; /* sequencer queue number */ + + snd_seq_addr_t addr; /* address of this device */ + + int seq_mode; /* sequencer mode */ + int file_mode; /* file access */ + + /* midi device table */ + int max_mididev; + + /* synth device table */ + int max_synthdev; + seq_oss_synthinfo_t synths[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS]; + int synth_opened; + + /* output queue */ + seq_oss_writeq_t *writeq; + + /* midi input queue */ + seq_oss_readq_t *readq; + + /* timer */ + seq_oss_timer_t *timer; +}; + + +/* + * function prototypes + */ + +/* create/delete OSS sequencer client */ +int snd_seq_oss_create_client(void); +int snd_seq_oss_delete_client(void); + +/* device file interface */ +int snd_seq_oss_open(struct file *file, int level); +void snd_seq_oss_release(seq_oss_devinfo_t *dp); +int snd_seq_oss_ioctl(seq_oss_devinfo_t *dp, unsigned int cmd, unsigned long arg); +int snd_seq_oss_read(seq_oss_devinfo_t *dev, char __user *buf, int count); +int snd_seq_oss_write(seq_oss_devinfo_t *dp, const char __user *buf, int count, struct file *opt); +unsigned int snd_seq_oss_poll(seq_oss_devinfo_t *dp, struct file *file, poll_table * wait); + +void snd_seq_oss_reset(seq_oss_devinfo_t *dp); +void snd_seq_oss_drain_write(seq_oss_devinfo_t *dp); + +/* */ +void snd_seq_oss_process_queue(seq_oss_devinfo_t *dp, abstime_t time); + + +/* proc interface */ +void snd_seq_oss_system_info_read(snd_info_buffer_t *buf); +void snd_seq_oss_midi_info_read(snd_info_buffer_t *buf); +void snd_seq_oss_synth_info_read(snd_info_buffer_t *buf); +void snd_seq_oss_readq_info_read(seq_oss_readq_t *q, snd_info_buffer_t *buf); + +/* file mode macros */ +#define is_read_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_READ) +#define is_write_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_WRITE) +#define is_nonblock_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_NONBLOCK) + +/* dispatch event */ +inline static int +snd_seq_oss_dispatch(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int atomic, int hop) +{ + return snd_seq_kernel_client_dispatch(dp->cseq, ev, atomic, hop); +} + +/* ioctl */ +inline static int +snd_seq_oss_control(seq_oss_devinfo_t *dp, unsigned int type, void *arg) +{ + return snd_seq_kernel_client_ctl(dp->cseq, type, arg); +} + +/* fill the addresses in header */ +inline static void +snd_seq_oss_fill_addr(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, + int dest_client, int dest_port) +{ + ev->queue = dp->queue; + ev->source = dp->addr; + ev->dest.client = dest_client; + ev->dest.port = dest_port; +} + + +/* misc. functions for proc interface */ +char *enabled_str(int bool); + + +/* for debug */ +#ifdef SNDRV_SEQ_OSS_DEBUG +extern int seq_oss_debug; +#define debug_printk(x) do { if (seq_oss_debug > 0) snd_printk x; } while (0) +#else +#define debug_printk(x) /**/ +#endif + +#endif /* __SEQ_OSS_DEVICE_H */ diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c new file mode 100644 index 000000000000..58e52ddd2927 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_event.c @@ -0,0 +1,447 @@ +/* + * OSS compatible sequencer driver + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_device.h" +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "seq_oss_event.h" +#include "seq_oss_timer.h" +#include <sound/seq_oss_legacy.h> +#include "seq_oss_readq.h" +#include "seq_oss_writeq.h" + + +/* + * prototypes + */ +static int extended_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev); +static int chn_voice_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev); +static int chn_common_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev); +static int timing_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev); +static int local_event(seq_oss_devinfo_t *dp, evrec_t *event_rec, snd_seq_event_t *ev); +static int old_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev); +static int note_on_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev); +static int note_off_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev); +static int set_note_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int note, int vel, snd_seq_event_t *ev); +static int set_control_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int param, int val, snd_seq_event_t *ev); +static int set_echo_event(seq_oss_devinfo_t *dp, evrec_t *rec, snd_seq_event_t *ev); + + +/* + * convert an OSS event to ALSA event + * return 0 : enqueued + * non-zero : invalid - ignored + */ + +int +snd_seq_oss_process_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev) +{ + switch (q->s.code) { + case SEQ_EXTENDED: + return extended_event(dp, q, ev); + + case EV_CHN_VOICE: + return chn_voice_event(dp, q, ev); + + case EV_CHN_COMMON: + return chn_common_event(dp, q, ev); + + case EV_TIMING: + return timing_event(dp, q, ev); + + case EV_SEQ_LOCAL: + return local_event(dp, q, ev); + + case EV_SYSEX: + return snd_seq_oss_synth_sysex(dp, q->x.dev, q->x.buf, ev); + + case SEQ_MIDIPUTC: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + /* put a midi byte */ + if (! is_write_mode(dp->file_mode)) + break; + if (snd_seq_oss_midi_open(dp, q->s.dev, SNDRV_SEQ_OSS_FILE_WRITE)) + break; + if (snd_seq_oss_midi_filemode(dp, q->s.dev) & SNDRV_SEQ_OSS_FILE_WRITE) + return snd_seq_oss_midi_putc(dp, q->s.dev, q->s.parm1, ev); + break; + + case SEQ_ECHO: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + return set_echo_event(dp, q, ev); + + case SEQ_PRIVATE: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + return snd_seq_oss_synth_raw_event(dp, q->c[1], q->c, ev); + + default: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + return old_event(dp, q, ev); + } + return -EINVAL; +} + +/* old type events: mode1 only */ +static int +old_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev) +{ + switch (q->s.code) { + case SEQ_NOTEOFF: + return note_off_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev); + + case SEQ_NOTEON: + return note_on_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev); + + case SEQ_WAIT: + /* skip */ + break; + + case SEQ_PGMCHANGE: + return set_control_event(dp, 0, SNDRV_SEQ_EVENT_PGMCHANGE, + q->n.chn, 0, q->n.note, ev); + + case SEQ_SYNCTIMER: + return snd_seq_oss_timer_reset(dp->timer); + } + + return -EINVAL; +} + +/* 8bytes extended event: mode1 only */ +static int +extended_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev) +{ + int val; + + switch (q->e.cmd) { + case SEQ_NOTEOFF: + return note_off_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev); + + case SEQ_NOTEON: + return note_on_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev); + + case SEQ_PGMCHANGE: + return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_PGMCHANGE, + q->e.chn, 0, q->e.p1, ev); + + case SEQ_AFTERTOUCH: + return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CHANPRESS, + q->e.chn, 0, q->e.p1, ev); + + case SEQ_BALANCE: + /* convert -128:127 to 0:127 */ + val = (char)q->e.p1; + val = (val + 128) / 2; + return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CONTROLLER, + q->e.chn, CTL_PAN, val, ev); + + case SEQ_CONTROLLER: + val = ((short)q->e.p3 << 8) | (short)q->e.p2; + switch (q->e.p1) { + case CTRL_PITCH_BENDER: /* SEQ1 V2 control */ + /* -0x2000:0x1fff */ + return set_control_event(dp, q->e.dev, + SNDRV_SEQ_EVENT_PITCHBEND, + q->e.chn, 0, val, ev); + case CTRL_PITCH_BENDER_RANGE: + /* conversion: 100/semitone -> 128/semitone */ + return set_control_event(dp, q->e.dev, + SNDRV_SEQ_EVENT_REGPARAM, + q->e.chn, 0, val*128/100, ev); + default: + return set_control_event(dp, q->e.dev, + SNDRV_SEQ_EVENT_CONTROL14, + q->e.chn, q->e.p1, val, ev); + } + + case SEQ_VOLMODE: + return snd_seq_oss_synth_raw_event(dp, q->e.dev, q->c, ev); + + } + return -EINVAL; +} + +/* channel voice events: mode1 and 2 */ +static int +chn_voice_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev) +{ + if (q->v.chn >= 32) + return -EINVAL; + switch (q->v.cmd) { + case MIDI_NOTEON: + return note_on_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev); + + case MIDI_NOTEOFF: + return note_off_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev); + + case MIDI_KEY_PRESSURE: + return set_note_event(dp, q->v.dev, SNDRV_SEQ_EVENT_KEYPRESS, + q->v.chn, q->v.note, q->v.parm, ev); + + } + return -EINVAL; +} + +/* channel common events: mode1 and 2 */ +static int +chn_common_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev) +{ + if (q->l.chn >= 32) + return -EINVAL; + switch (q->l.cmd) { + case MIDI_PGM_CHANGE: + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PGMCHANGE, + q->l.chn, 0, q->l.p1, ev); + + case MIDI_CTL_CHANGE: + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CONTROLLER, + q->l.chn, q->l.p1, q->l.val, ev); + + case MIDI_PITCH_BEND: + /* conversion: 0:0x3fff -> -0x2000:0x1fff */ + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PITCHBEND, + q->l.chn, 0, q->l.val - 8192, ev); + + case MIDI_CHN_PRESSURE: + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CHANPRESS, + q->l.chn, 0, q->l.val, ev); + } + return -EINVAL; +} + +/* timer events: mode1 and mode2 */ +static int +timing_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev) +{ + switch (q->t.cmd) { + case TMR_ECHO: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return set_echo_event(dp, q, ev); + else { + evrec_t tmp; + memset(&tmp, 0, sizeof(tmp)); + /* XXX: only for little-endian! */ + tmp.echo = (q->t.time << 8) | SEQ_ECHO; + return set_echo_event(dp, &tmp, ev); + } + + case TMR_STOP: + if (dp->seq_mode) + return snd_seq_oss_timer_stop(dp->timer); + return 0; + + case TMR_CONTINUE: + if (dp->seq_mode) + return snd_seq_oss_timer_continue(dp->timer); + return 0; + + case TMR_TEMPO: + if (dp->seq_mode) + return snd_seq_oss_timer_tempo(dp->timer, q->t.time); + return 0; + } + + return -EINVAL; +} + +/* local events: mode1 and 2 */ +static int +local_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev) +{ + return -EINVAL; +} + +/* + * process note-on event for OSS synth + * three different modes are available: + * - SNDRV_SEQ_OSS_PROCESS_EVENTS (for one-voice per channel mode) + * Accept note 255 as volume change. + * - SNDRV_SEQ_OSS_PASS_EVENTS + * Pass all events to lowlevel driver anyway + * - SNDRV_SEQ_OSS_PROCESS_KEYPRESS (mostly for Emu8000) + * Use key-pressure if note >= 128 + */ +static int +note_on_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev) +{ + seq_oss_synthinfo_t *info = &dp->synths[dev]; + switch (info->arg.event_passing) { + case SNDRV_SEQ_OSS_PROCESS_EVENTS: + if (! info->ch || ch < 0 || ch >= info->nr_voices) { + /* pass directly */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + } + + if (note == 255 && info->ch[ch].note >= 0) { + /* volume control */ + int type; + //if (! vel) + /* set volume to zero -- note off */ + // type = SNDRV_SEQ_EVENT_NOTEOFF; + //else + if (info->ch[ch].vel) + /* sample already started -- volume change */ + type = SNDRV_SEQ_EVENT_KEYPRESS; + else + /* sample not started -- start now */ + type = SNDRV_SEQ_EVENT_NOTEON; + info->ch[ch].vel = vel; + return set_note_event(dp, dev, type, ch, info->ch[ch].note, vel, ev); + } else if (note >= 128) + return -EINVAL; /* invalid */ + + if (note != info->ch[ch].note && info->ch[ch].note >= 0) + /* note changed - note off at beginning */ + set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, info->ch[ch].note, 0, ev); + /* set current status */ + info->ch[ch].note = note; + info->ch[ch].vel = vel; + if (vel) /* non-zero velocity - start the note now */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + return -EINVAL; + + case SNDRV_SEQ_OSS_PASS_EVENTS: + /* pass the event anyway */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + + case SNDRV_SEQ_OSS_PROCESS_KEYPRESS: + if (note >= 128) /* key pressure: shifted by 128 */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_KEYPRESS, ch, note - 128, vel, ev); + else /* normal note-on event */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + } + return -EINVAL; +} + +/* + * process note-off event for OSS synth + */ +static int +note_off_event(seq_oss_devinfo_t *dp, int dev, int ch, int note, int vel, snd_seq_event_t *ev) +{ + seq_oss_synthinfo_t *info = &dp->synths[dev]; + switch (info->arg.event_passing) { + case SNDRV_SEQ_OSS_PROCESS_EVENTS: + if (! info->ch || ch < 0 || ch >= info->nr_voices) { + /* pass directly */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + } + + if (info->ch[ch].note >= 0) { + note = info->ch[ch].note; + info->ch[ch].vel = 0; + info->ch[ch].note = -1; + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev); + } + return -EINVAL; /* invalid */ + + case SNDRV_SEQ_OSS_PASS_EVENTS: + case SNDRV_SEQ_OSS_PROCESS_KEYPRESS: + /* pass the event anyway */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev); + + } + return -EINVAL; +} + +/* + * create a note event + */ +static int +set_note_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int note, int vel, snd_seq_event_t *ev) +{ + if (! snd_seq_oss_synth_is_valid(dp, dev)) + return -ENXIO; + + ev->type = type; + snd_seq_oss_synth_addr(dp, dev, ev); + ev->data.note.channel = ch; + ev->data.note.note = note; + ev->data.note.velocity = vel; + + return 0; +} + +/* + * create a control event + */ +static int +set_control_event(seq_oss_devinfo_t *dp, int dev, int type, int ch, int param, int val, snd_seq_event_t *ev) +{ + if (! snd_seq_oss_synth_is_valid(dp, dev)) + return -ENXIO; + + ev->type = type; + snd_seq_oss_synth_addr(dp, dev, ev); + ev->data.control.channel = ch; + ev->data.control.param = param; + ev->data.control.value = val; + + return 0; +} + +/* + * create an echo event + */ +static int +set_echo_event(seq_oss_devinfo_t *dp, evrec_t *rec, snd_seq_event_t *ev) +{ + ev->type = SNDRV_SEQ_EVENT_ECHO; + /* echo back to itself */ + snd_seq_oss_fill_addr(dp, ev, dp->addr.client, dp->addr.port); + memcpy(&ev->data, rec, LONG_EVENT_SIZE); + return 0; +} + +/* + * event input callback from ALSA sequencer: + * the echo event is processed here. + */ +int +snd_seq_oss_event_input(snd_seq_event_t *ev, int direct, void *private_data, + int atomic, int hop) +{ + seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private_data; + evrec_t *rec; + + if (ev->type != SNDRV_SEQ_EVENT_ECHO) + return snd_seq_oss_midi_input(ev, direct, private_data); + + if (ev->source.client != dp->cseq) + return 0; /* ignored */ + + rec = (evrec_t*)&ev->data; + if (rec->s.code == SEQ_SYNCTIMER) { + /* sync echo back */ + snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time); + + } else { + /* echo back event */ + if (dp->readq == NULL) + return 0; + snd_seq_oss_readq_put_event(dp->readq, rec); + } + return 0; +} + diff --git a/sound/core/seq/oss/seq_oss_event.h b/sound/core/seq/oss/seq_oss_event.h new file mode 100644 index 000000000000..bf1d4d3f53c9 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_event.h @@ -0,0 +1,112 @@ +/* + * OSS compatible sequencer driver + * + * seq_oss_event.h - OSS event queue record + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_EVENT_H +#define __SEQ_OSS_EVENT_H + +#include "seq_oss_device.h" + +#define SHORT_EVENT_SIZE 4 +#define LONG_EVENT_SIZE 8 + +/* short event (4bytes) */ +typedef struct evrec_short_t { + unsigned char code; + unsigned char parm1; + unsigned char dev; + unsigned char parm2; +} evrec_short_t; + +/* short note events (4bytes) */ +typedef struct evrec_note_t { + unsigned char code; + unsigned char chn; + unsigned char note; + unsigned char vel; +} evrec_note_t; + +/* long timer events (8bytes) */ +typedef struct evrec_timer_t { + unsigned char code; + unsigned char cmd; + unsigned char dummy1, dummy2; + unsigned int time; +} evrec_timer_t; + +/* long extended events (8bytes) */ +typedef struct evrec_extended_t { + unsigned char code; + unsigned char cmd; + unsigned char dev; + unsigned char chn; + unsigned char p1, p2, p3, p4; +} evrec_extended_t; + +/* long channel events (8bytes) */ +typedef struct evrec_long_t { + unsigned char code; + unsigned char dev; + unsigned char cmd; + unsigned char chn; + unsigned char p1, p2; + unsigned short val; +} evrec_long_t; + +/* channel voice events (8bytes) */ +typedef struct evrec_voice_t { + unsigned char code; + unsigned char dev; + unsigned char cmd; + unsigned char chn; + unsigned char note, parm; + unsigned short dummy; +} evrec_voice_t; + +/* sysex events (8bytes) */ +typedef struct evrec_sysex_t { + unsigned char code; + unsigned char dev; + unsigned char buf[6]; +} evrec_sysex_t; + +/* event record */ +union evrec_t { + evrec_short_t s; + evrec_note_t n; + evrec_long_t l; + evrec_voice_t v; + evrec_timer_t t; + evrec_extended_t e; + evrec_sysex_t x; + unsigned int echo; + unsigned char c[LONG_EVENT_SIZE]; +}; + +#define ev_is_long(ev) ((ev)->s.code >= 128) +#define ev_length(ev) ((ev)->s.code >= 128 ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE) + +int snd_seq_oss_process_event(seq_oss_devinfo_t *dp, evrec_t *q, snd_seq_event_t *ev); +int snd_seq_oss_process_timer_event(seq_oss_timer_t *rec, evrec_t *q); +int snd_seq_oss_event_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop); + + +#endif /* __SEQ_OSS_EVENT_H */ diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c new file mode 100644 index 000000000000..bac4b4f1a94e --- /dev/null +++ b/sound/core/seq/oss/seq_oss_init.c @@ -0,0 +1,555 @@ +/* + * OSS compatible sequencer driver + * + * open/close and reset interface + * + * Copyright (C) 1998-1999 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_device.h" +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "seq_oss_writeq.h" +#include "seq_oss_readq.h" +#include "seq_oss_timer.h" +#include "seq_oss_event.h" +#include <linux/init.h> +#include <linux/moduleparam.h> + +/* + * common variables + */ +static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN; +module_param(maxqlen, int, 0444); +MODULE_PARM_DESC(maxqlen, "maximum queue length"); + +static int system_client = -1; /* ALSA sequencer client number */ +static int system_port = -1; + +static int num_clients; +static seq_oss_devinfo_t *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS]; + + +/* + * prototypes + */ +static int receive_announce(snd_seq_event_t *ev, int direct, void *private, int atomic, int hop); +static int translate_mode(struct file *file); +static int create_port(seq_oss_devinfo_t *dp); +static int delete_port(seq_oss_devinfo_t *dp); +static int alloc_seq_queue(seq_oss_devinfo_t *dp); +static int delete_seq_queue(int queue); +static void free_devinfo(void *private); + +#define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec) + + +/* + * create sequencer client for OSS sequencer + */ +int __init +snd_seq_oss_create_client(void) +{ + int rc; + snd_seq_client_callback_t callback; + snd_seq_client_info_t *info; + snd_seq_port_info_t *port; + snd_seq_port_callback_t port_callback; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + port = kmalloc(sizeof(*port), GFP_KERNEL); + if (!info || !port) { + rc = -ENOMEM; + goto __error; + } + + /* create ALSA client */ + memset(&callback, 0, sizeof(callback)); + + callback.private_data = NULL; + callback.allow_input = 1; + callback.allow_output = 1; + + rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS, &callback); + if (rc < 0) + goto __error; + + system_client = rc; + debug_printk(("new client = %d\n", rc)); + + /* set client information */ + memset(info, 0, sizeof(*info)); + info->client = system_client; + info->type = KERNEL_CLIENT; + strcpy(info->name, "OSS sequencer"); + + rc = call_ctl(SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, info); + + /* look up midi devices */ + snd_seq_oss_midi_lookup_ports(system_client); + + /* create annoucement receiver port */ + memset(port, 0, sizeof(*port)); + strcpy(port->name, "Receiver"); + port->addr.client = system_client; + port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */ + port->type = 0; + + memset(&port_callback, 0, sizeof(port_callback)); + /* don't set port_callback.owner here. otherwise the module counter + * is incremented and we can no longer release the module.. + */ + port_callback.event_input = receive_announce; + port->kernel = &port_callback; + + call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port); + if ((system_port = port->addr.port) >= 0) { + snd_seq_port_subscribe_t subs; + + memset(&subs, 0, sizeof(subs)); + subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM; + subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + subs.dest.client = system_client; + subs.dest.port = system_port; + call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs); + } + rc = 0; + + __error: + kfree(port); + kfree(info); + return rc; +} + + +/* + * receive annoucement from system port, and check the midi device + */ +static int +receive_announce(snd_seq_event_t *ev, int direct, void *private, int atomic, int hop) +{ + snd_seq_port_info_t pinfo; + + if (atomic) + return 0; /* it must not happen */ + + switch (ev->type) { + case SNDRV_SEQ_EVENT_PORT_START: + case SNDRV_SEQ_EVENT_PORT_CHANGE: + if (ev->data.addr.client == system_client) + break; /* ignore myself */ + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.addr = ev->data.addr; + if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0) + snd_seq_oss_midi_check_new_port(&pinfo); + break; + + case SNDRV_SEQ_EVENT_PORT_EXIT: + if (ev->data.addr.client == system_client) + break; /* ignore myself */ + snd_seq_oss_midi_check_exit_port(ev->data.addr.client, + ev->data.addr.port); + break; + } + return 0; +} + + +/* + * delete OSS sequencer client + */ +int +snd_seq_oss_delete_client(void) +{ + if (system_client >= 0) + snd_seq_delete_kernel_client(system_client); + + snd_seq_oss_midi_clear_all(); + + return 0; +} + + +/* + * open sequencer device + */ +int +snd_seq_oss_open(struct file *file, int level) +{ + int i, rc; + seq_oss_devinfo_t *dp; + + if ((dp = kcalloc(1, sizeof(*dp), GFP_KERNEL)) == NULL) { + snd_printk(KERN_ERR "can't malloc device info\n"); + return -ENOMEM; + } + debug_printk(("oss_open: dp = %p\n", dp)); + + for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) { + if (client_table[i] == NULL) + break; + } + if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) { + snd_printk(KERN_ERR "too many applications\n"); + kfree(dp); + return -ENOMEM; + } + + dp->index = i; + dp->cseq = system_client; + dp->port = -1; + dp->queue = -1; + dp->readq = NULL; + dp->writeq = NULL; + + /* look up synth and midi devices */ + snd_seq_oss_synth_setup(dp); + snd_seq_oss_midi_setup(dp); + + if (dp->synth_opened == 0 && dp->max_mididev == 0) { + /* snd_printk(KERN_ERR "no device found\n"); */ + rc = -ENODEV; + goto _error; + } + + /* create port */ + debug_printk(("create new port\n")); + if ((rc = create_port(dp)) < 0) { + snd_printk(KERN_ERR "can't create port\n"); + goto _error; + } + + /* allocate queue */ + debug_printk(("allocate queue\n")); + if ((rc = alloc_seq_queue(dp)) < 0) + goto _error; + + /* set address */ + dp->addr.client = dp->cseq; + dp->addr.port = dp->port; + /*dp->addr.queue = dp->queue;*/ + /*dp->addr.channel = 0;*/ + + dp->seq_mode = level; + + /* set up file mode */ + dp->file_mode = translate_mode(file); + + /* initialize read queue */ + debug_printk(("initialize read queue\n")); + if (is_read_mode(dp->file_mode)) { + if ((dp->readq = snd_seq_oss_readq_new(dp, maxqlen)) == NULL) { + rc = -ENOMEM; + goto _error; + } + } + + /* initialize write queue */ + debug_printk(("initialize write queue\n")); + if (is_write_mode(dp->file_mode)) { + dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen); + if (dp->writeq == NULL) { + rc = -ENOMEM; + goto _error; + } + } + + /* initialize timer */ + debug_printk(("initialize timer\n")); + if ((dp->timer = snd_seq_oss_timer_new(dp)) == NULL) { + snd_printk(KERN_ERR "can't alloc timer\n"); + rc = -ENOMEM; + goto _error; + } + debug_printk(("timer initialized\n")); + + /* set private data pointer */ + file->private_data = dp; + + /* set up for mode2 */ + if (level == SNDRV_SEQ_OSS_MODE_MUSIC) + snd_seq_oss_synth_setup_midi(dp); + else if (is_read_mode(dp->file_mode)) + snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ); + + client_table[dp->index] = dp; + num_clients++; + + debug_printk(("open done\n")); + return 0; + + _error: + snd_seq_oss_synth_cleanup(dp); + snd_seq_oss_midi_cleanup(dp); + i = dp->queue; + delete_port(dp); + delete_seq_queue(i); + + return rc; +} + +/* + * translate file flags to private mode + */ +static int +translate_mode(struct file *file) +{ + int file_mode = 0; + if ((file->f_flags & O_ACCMODE) != O_RDONLY) + file_mode |= SNDRV_SEQ_OSS_FILE_WRITE; + if ((file->f_flags & O_ACCMODE) != O_WRONLY) + file_mode |= SNDRV_SEQ_OSS_FILE_READ; + if (file->f_flags & O_NONBLOCK) + file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK; + return file_mode; +} + + +/* + * create sequencer port + */ +static int +create_port(seq_oss_devinfo_t *dp) +{ + int rc; + snd_seq_port_info_t port; + snd_seq_port_callback_t callback; + + memset(&port, 0, sizeof(port)); + port.addr.client = dp->cseq; + sprintf(port.name, "Sequencer-%d", dp->index); + port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */ + port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC; + port.midi_channels = 128; + port.synth_voices = 128; + + memset(&callback, 0, sizeof(callback)); + callback.owner = THIS_MODULE; + callback.private_data = dp; + callback.event_input = snd_seq_oss_event_input; + callback.private_free = free_devinfo; + port.kernel = &callback; + + rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port); + if (rc < 0) + return rc; + + dp->port = port.addr.port; + debug_printk(("new port = %d\n", port.addr.port)); + + return 0; +} + +/* + * delete ALSA port + */ +static int +delete_port(seq_oss_devinfo_t *dp) +{ + if (dp->port < 0) + return 0; + + debug_printk(("delete_port %i\n", dp->port)); + return snd_seq_event_port_detach(dp->cseq, dp->port); +} + +/* + * allocate a queue + */ +static int +alloc_seq_queue(seq_oss_devinfo_t *dp) +{ + snd_seq_queue_info_t qinfo; + int rc; + + memset(&qinfo, 0, sizeof(qinfo)); + qinfo.owner = system_client; + qinfo.locked = 1; + strcpy(qinfo.name, "OSS Sequencer Emulation"); + if ((rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo)) < 0) + return rc; + dp->queue = qinfo.queue; + return 0; +} + +/* + * release queue + */ +static int +delete_seq_queue(int queue) +{ + snd_seq_queue_info_t qinfo; + int rc; + + if (queue < 0) + return 0; + memset(&qinfo, 0, sizeof(qinfo)); + qinfo.queue = queue; + rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo); + if (rc < 0) + printk(KERN_ERR "seq-oss: unable to delete queue %d (%d)\n", queue, rc); + return rc; +} + + +/* + * free device informations - private_free callback of port + */ +static void +free_devinfo(void *private) +{ + seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private; + + if (dp->timer) + snd_seq_oss_timer_delete(dp->timer); + + if (dp->writeq) + snd_seq_oss_writeq_delete(dp->writeq); + + if (dp->readq) + snd_seq_oss_readq_delete(dp->readq); + + kfree(dp); +} + + +/* + * close sequencer device + */ +void +snd_seq_oss_release(seq_oss_devinfo_t *dp) +{ + int queue; + + client_table[dp->index] = NULL; + num_clients--; + + debug_printk(("resetting..\n")); + snd_seq_oss_reset(dp); + + debug_printk(("cleaning up..\n")); + snd_seq_oss_synth_cleanup(dp); + snd_seq_oss_midi_cleanup(dp); + + /* clear slot */ + debug_printk(("releasing resource..\n")); + queue = dp->queue; + if (dp->port >= 0) + delete_port(dp); + delete_seq_queue(queue); + + debug_printk(("release done\n")); +} + + +/* + * Wait until the queue is empty (if we don't have nonblock) + */ +void +snd_seq_oss_drain_write(seq_oss_devinfo_t *dp) +{ + if (! dp->timer->running) + return; + if (is_write_mode(dp->file_mode) && !is_nonblock_mode(dp->file_mode) && + dp->writeq) { + debug_printk(("syncing..\n")); + while (snd_seq_oss_writeq_sync(dp->writeq)) + ; + } +} + + +/* + * reset sequencer devices + */ +void +snd_seq_oss_reset(seq_oss_devinfo_t *dp) +{ + int i; + + /* reset all synth devices */ + for (i = 0; i < dp->max_synthdev; i++) + snd_seq_oss_synth_reset(dp, i); + + /* reset all midi devices */ + if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) { + for (i = 0; i < dp->max_mididev; i++) + snd_seq_oss_midi_reset(dp, i); + } + + /* remove queues */ + if (dp->readq) + snd_seq_oss_readq_clear(dp->readq); + if (dp->writeq) + snd_seq_oss_writeq_clear(dp->writeq); + + /* reset timer */ + snd_seq_oss_timer_stop(dp->timer); +} + + +/* + * misc. functions for proc interface + */ +char * +enabled_str(int bool) +{ + return bool ? "enabled" : "disabled"; +} + +static char * +filemode_str(int val) +{ + static char *str[] = { + "none", "read", "write", "read/write", + }; + return str[val & SNDRV_SEQ_OSS_FILE_ACMODE]; +} + + +/* + * proc interface + */ +void +snd_seq_oss_system_info_read(snd_info_buffer_t *buf) +{ + int i; + seq_oss_devinfo_t *dp; + + snd_iprintf(buf, "ALSA client number %d\n", system_client); + snd_iprintf(buf, "ALSA receiver port %d\n", system_port); + + snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients); + for (i = 0; i < num_clients; i++) { + snd_iprintf(buf, "\nApplication %d: ", i); + if ((dp = client_table[i]) == NULL) { + snd_iprintf(buf, "*empty*\n"); + continue; + } + snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue); + snd_iprintf(buf, " sequencer mode = %s : file open mode = %s\n", + (dp->seq_mode ? "music" : "synth"), + filemode_str(dp->file_mode)); + if (dp->seq_mode) + snd_iprintf(buf, " timer tempo = %d, timebase = %d\n", + dp->timer->oss_tempo, dp->timer->oss_timebase); + snd_iprintf(buf, " max queue length %d\n", maxqlen); + if (is_read_mode(dp->file_mode) && dp->readq) + snd_seq_oss_readq_info_read(dp->readq, buf); + } +} + diff --git a/sound/core/seq/oss/seq_oss_ioctl.c b/sound/core/seq/oss/seq_oss_ioctl.c new file mode 100644 index 000000000000..e86f18d00f39 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_ioctl.c @@ -0,0 +1,209 @@ +/* + * OSS compatible sequencer driver + * + * OSS compatible i/o control + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_device.h" +#include "seq_oss_readq.h" +#include "seq_oss_writeq.h" +#include "seq_oss_timer.h" +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "seq_oss_event.h" + +static int snd_seq_oss_synth_info_user(seq_oss_devinfo_t *dp, void __user *arg) +{ + struct synth_info info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + if (snd_seq_oss_synth_make_info(dp, info.device, &info) < 0) + return -EINVAL; + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_seq_oss_midi_info_user(seq_oss_devinfo_t *dp, void __user *arg) +{ + struct midi_info info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + if (snd_seq_oss_midi_make_info(dp, info.device, &info) < 0) + return -EINVAL; + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_seq_oss_oob_user(seq_oss_devinfo_t *dp, void __user *arg) +{ + unsigned char ev[8]; + snd_seq_event_t tmpev; + + if (copy_from_user(ev, arg, 8)) + return -EFAULT; + memset(&tmpev, 0, sizeof(tmpev)); + snd_seq_oss_fill_addr(dp, &tmpev, dp->addr.port, dp->addr.client); + tmpev.time.tick = 0; + if (! snd_seq_oss_process_event(dp, (evrec_t*)ev, &tmpev)) { + snd_seq_oss_dispatch(dp, &tmpev, 0, 0); + } + return 0; +} + +int +snd_seq_oss_ioctl(seq_oss_devinfo_t *dp, unsigned int cmd, unsigned long carg) +{ + int dev, val; + void __user *arg = (void __user *)carg; + int __user *p = arg; + + switch (cmd) { + case SNDCTL_TMR_TIMEBASE: + case SNDCTL_TMR_TEMPO: + case SNDCTL_TMR_START: + case SNDCTL_TMR_STOP: + case SNDCTL_TMR_CONTINUE: + case SNDCTL_TMR_METRONOME: + case SNDCTL_TMR_SOURCE: + case SNDCTL_TMR_SELECT: + case SNDCTL_SEQ_CTRLRATE: + return snd_seq_oss_timer_ioctl(dp->timer, cmd, arg); + + case SNDCTL_SEQ_PANIC: + debug_printk(("panic\n")); + snd_seq_oss_reset(dp); + return -EINVAL; + + case SNDCTL_SEQ_SYNC: + debug_printk(("sync\n")); + if (! is_write_mode(dp->file_mode) || dp->writeq == NULL) + return 0; + while (snd_seq_oss_writeq_sync(dp->writeq)) + ; + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; + + case SNDCTL_SEQ_RESET: + debug_printk(("reset\n")); + snd_seq_oss_reset(dp); + return 0; + + case SNDCTL_SEQ_TESTMIDI: + debug_printk(("test midi\n")); + if (get_user(dev, p)) + return -EFAULT; + return snd_seq_oss_midi_open(dp, dev, dp->file_mode); + + case SNDCTL_SEQ_GETINCOUNT: + debug_printk(("get in count\n")); + if (dp->readq == NULL || ! is_read_mode(dp->file_mode)) + return 0; + return put_user(dp->readq->qlen, p) ? -EFAULT : 0; + + case SNDCTL_SEQ_GETOUTCOUNT: + debug_printk(("get out count\n")); + if (! is_write_mode(dp->file_mode) || dp->writeq == NULL) + return 0; + return put_user(snd_seq_oss_writeq_get_free_size(dp->writeq), p) ? -EFAULT : 0; + + case SNDCTL_SEQ_GETTIME: + debug_printk(("get time\n")); + return put_user(snd_seq_oss_timer_cur_tick(dp->timer), p) ? -EFAULT : 0; + + case SNDCTL_SEQ_RESETSAMPLES: + debug_printk(("reset samples\n")); + if (get_user(dev, p)) + return -EFAULT; + return snd_seq_oss_synth_ioctl(dp, dev, cmd, carg); + + case SNDCTL_SEQ_NRSYNTHS: + debug_printk(("nr synths\n")); + return put_user(dp->max_synthdev, p) ? -EFAULT : 0; + + case SNDCTL_SEQ_NRMIDIS: + debug_printk(("nr midis\n")); + return put_user(dp->max_mididev, p) ? -EFAULT : 0; + + case SNDCTL_SYNTH_MEMAVL: + debug_printk(("mem avail\n")); + if (get_user(dev, p)) + return -EFAULT; + val = snd_seq_oss_synth_ioctl(dp, dev, cmd, carg); + return put_user(val, p) ? -EFAULT : 0; + + case SNDCTL_FM_4OP_ENABLE: + debug_printk(("4op\n")); + if (get_user(dev, p)) + return -EFAULT; + snd_seq_oss_synth_ioctl(dp, dev, cmd, carg); + return 0; + + case SNDCTL_SYNTH_INFO: + case SNDCTL_SYNTH_ID: + debug_printk(("synth info\n")); + return snd_seq_oss_synth_info_user(dp, arg); + + case SNDCTL_SEQ_OUTOFBAND: + debug_printk(("out of band\n")); + return snd_seq_oss_oob_user(dp, arg); + + case SNDCTL_MIDI_INFO: + debug_printk(("midi info\n")); + return snd_seq_oss_midi_info_user(dp, arg); + + case SNDCTL_SEQ_THRESHOLD: + debug_printk(("threshold\n")); + if (! is_write_mode(dp->file_mode)) + return 0; + if (get_user(val, p)) + return -EFAULT; + if (val < 1) + val = 1; + if (val >= dp->writeq->maxlen) + val = dp->writeq->maxlen - 1; + snd_seq_oss_writeq_set_output(dp->writeq, val); + return 0; + + case SNDCTL_MIDI_PRETIME: + debug_printk(("pretime\n")); + if (dp->readq == NULL || !is_read_mode(dp->file_mode)) + return 0; + if (get_user(val, p)) + return -EFAULT; + if (val <= 0) + val = -1; + else + val = (HZ * val) / 10; + dp->readq->pre_event_timeout = val; + return put_user(val, p) ? -EFAULT : 0; + + default: + debug_printk(("others\n")); + if (! is_write_mode(dp->file_mode)) + return -EIO; + return snd_seq_oss_synth_ioctl(dp, 0, cmd, carg); + } + return 0; +} + diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c new file mode 100644 index 000000000000..9aece6c65dbc --- /dev/null +++ b/sound/core/seq/oss/seq_oss_midi.c @@ -0,0 +1,710 @@ +/* + * OSS compatible sequencer driver + * + * MIDI device handlers + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_midi.h" +#include "seq_oss_readq.h" +#include "seq_oss_timer.h" +#include "seq_oss_event.h" +#include <sound/seq_midi_event.h> +#include "../seq_lock.h" +#include <linux/init.h> + + +/* + * constants + */ +#define SNDRV_SEQ_OSS_MAX_MIDI_NAME 30 + +/* + * definition of midi device record + */ +struct seq_oss_midi_t { + int seq_device; /* device number */ + int client; /* sequencer client number */ + int port; /* sequencer port number */ + unsigned int flags; /* port capability */ + int opened; /* flag for opening */ + unsigned char name[SNDRV_SEQ_OSS_MAX_MIDI_NAME]; + snd_midi_event_t *coder; /* MIDI event coder */ + seq_oss_devinfo_t *devinfo; /* assigned OSSseq device */ + snd_use_lock_t use_lock; +}; + + +/* + * midi device table + */ +static int max_midi_devs; +static seq_oss_midi_t *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS]; + +static DEFINE_SPINLOCK(register_lock); + +/* + * prototypes + */ +static seq_oss_midi_t *get_mdev(int dev); +static seq_oss_midi_t *get_mididev(seq_oss_devinfo_t *dp, int dev); +static int send_synth_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int dev); +static int send_midi_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, seq_oss_midi_t *mdev); + +/* + * look up the existing ports + * this looks a very exhausting job. + */ +int __init +snd_seq_oss_midi_lookup_ports(int client) +{ + snd_seq_client_info_t *clinfo; + snd_seq_port_info_t *pinfo; + + clinfo = kcalloc(1, sizeof(*clinfo), GFP_KERNEL); + pinfo = kcalloc(1, sizeof(*pinfo), GFP_KERNEL); + if (! clinfo || ! pinfo) { + kfree(clinfo); + kfree(pinfo); + return -ENOMEM; + } + clinfo->client = -1; + while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, clinfo) == 0) { + if (clinfo->client == client) + continue; /* ignore myself */ + pinfo->addr.client = clinfo->client; + pinfo->addr.port = -1; + while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, pinfo) == 0) + snd_seq_oss_midi_check_new_port(pinfo); + } + kfree(clinfo); + kfree(pinfo); + return 0; +} + + +/* + */ +static seq_oss_midi_t * +get_mdev(int dev) +{ + seq_oss_midi_t *mdev; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + mdev = midi_devs[dev]; + if (mdev) + snd_use_lock_use(&mdev->use_lock); + spin_unlock_irqrestore(®ister_lock, flags); + return mdev; +} + +/* + * look for the identical slot + */ +static seq_oss_midi_t * +find_slot(int client, int port) +{ + int i; + seq_oss_midi_t *mdev; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_midi_devs; i++) { + mdev = midi_devs[i]; + if (mdev && mdev->client == client && mdev->port == port) { + /* found! */ + snd_use_lock_use(&mdev->use_lock); + spin_unlock_irqrestore(®ister_lock, flags); + return mdev; + } + } + spin_unlock_irqrestore(®ister_lock, flags); + return NULL; +} + + +#define PERM_WRITE (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE) +#define PERM_READ (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ) +/* + * register a new port if it doesn't exist yet + */ +int +snd_seq_oss_midi_check_new_port(snd_seq_port_info_t *pinfo) +{ + int i; + seq_oss_midi_t *mdev; + unsigned long flags; + + debug_printk(("check for MIDI client %d port %d\n", pinfo->addr.client, pinfo->addr.port)); + /* the port must include generic midi */ + if (! (pinfo->type & SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC)) + return 0; + /* either read or write subscribable */ + if ((pinfo->capability & PERM_WRITE) != PERM_WRITE && + (pinfo->capability & PERM_READ) != PERM_READ) + return 0; + + /* + * look for the identical slot + */ + if ((mdev = find_slot(pinfo->addr.client, pinfo->addr.port)) != NULL) { + /* already exists */ + snd_use_lock_free(&mdev->use_lock); + return 0; + } + + /* + * allocate midi info record + */ + if ((mdev = kcalloc(1, sizeof(*mdev), GFP_KERNEL)) == NULL) { + snd_printk(KERN_ERR "can't malloc midi info\n"); + return -ENOMEM; + } + + /* copy the port information */ + mdev->client = pinfo->addr.client; + mdev->port = pinfo->addr.port; + mdev->flags = pinfo->capability; + mdev->opened = 0; + snd_use_lock_init(&mdev->use_lock); + + /* copy and truncate the name of synth device */ + strlcpy(mdev->name, pinfo->name, sizeof(mdev->name)); + + /* create MIDI coder */ + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &mdev->coder) < 0) { + snd_printk(KERN_ERR "can't malloc midi coder\n"); + kfree(mdev); + return -ENOMEM; + } + /* OSS sequencer adds running status to all sequences */ + snd_midi_event_no_status(mdev->coder, 1); + + /* + * look for en empty slot + */ + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_midi_devs; i++) { + if (midi_devs[i] == NULL) + break; + } + if (i >= max_midi_devs) { + if (max_midi_devs >= SNDRV_SEQ_OSS_MAX_MIDI_DEVS) { + spin_unlock_irqrestore(®ister_lock, flags); + snd_midi_event_free(mdev->coder); + kfree(mdev); + return -ENOMEM; + } + max_midi_devs++; + } + mdev->seq_device = i; + midi_devs[mdev->seq_device] = mdev; + spin_unlock_irqrestore(®ister_lock, flags); + + return 0; +} + +/* + * release the midi device if it was registered + */ +int +snd_seq_oss_midi_check_exit_port(int client, int port) +{ + seq_oss_midi_t *mdev; + unsigned long flags; + int index; + + if ((mdev = find_slot(client, port)) != NULL) { + spin_lock_irqsave(®ister_lock, flags); + midi_devs[mdev->seq_device] = NULL; + spin_unlock_irqrestore(®ister_lock, flags); + snd_use_lock_free(&mdev->use_lock); + snd_use_lock_sync(&mdev->use_lock); + if (mdev->coder) + snd_midi_event_free(mdev->coder); + kfree(mdev); + } + spin_lock_irqsave(®ister_lock, flags); + for (index = max_midi_devs - 1; index >= 0; index--) { + if (midi_devs[index]) + break; + } + max_midi_devs = index + 1; + spin_unlock_irqrestore(®ister_lock, flags); + return 0; +} + + +/* + * release the midi device if it was registered + */ +void +snd_seq_oss_midi_clear_all(void) +{ + int i; + seq_oss_midi_t *mdev; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_midi_devs; i++) { + if ((mdev = midi_devs[i]) != NULL) { + if (mdev->coder) + snd_midi_event_free(mdev->coder); + kfree(mdev); + midi_devs[i] = NULL; + } + } + max_midi_devs = 0; + spin_unlock_irqrestore(®ister_lock, flags); +} + + +/* + * set up midi tables + */ +void +snd_seq_oss_midi_setup(seq_oss_devinfo_t *dp) +{ + dp->max_mididev = max_midi_devs; +} + +/* + * clean up midi tables + */ +void +snd_seq_oss_midi_cleanup(seq_oss_devinfo_t *dp) +{ + int i; + for (i = 0; i < dp->max_mididev; i++) + snd_seq_oss_midi_close(dp, i); + dp->max_mididev = 0; +} + + +/* + * open all midi devices. ignore errors. + */ +void +snd_seq_oss_midi_open_all(seq_oss_devinfo_t *dp, int file_mode) +{ + int i; + for (i = 0; i < dp->max_mididev; i++) + snd_seq_oss_midi_open(dp, i, file_mode); +} + + +/* + * get the midi device information + */ +static seq_oss_midi_t * +get_mididev(seq_oss_devinfo_t *dp, int dev) +{ + if (dev < 0 || dev >= dp->max_mididev) + return NULL; + return get_mdev(dev); +} + + +/* + * open the midi device if not opened yet + */ +int +snd_seq_oss_midi_open(seq_oss_devinfo_t *dp, int dev, int fmode) +{ + int perm; + seq_oss_midi_t *mdev; + snd_seq_port_subscribe_t subs; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return -ENODEV; + + /* already used? */ + if (mdev->opened && mdev->devinfo != dp) { + snd_use_lock_free(&mdev->use_lock); + return -EBUSY; + } + + perm = 0; + if (is_write_mode(fmode)) + perm |= PERM_WRITE; + if (is_read_mode(fmode)) + perm |= PERM_READ; + perm &= mdev->flags; + if (perm == 0) { + snd_use_lock_free(&mdev->use_lock); + return -ENXIO; + } + + /* already opened? */ + if ((mdev->opened & perm) == perm) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } + + perm &= ~mdev->opened; + + memset(&subs, 0, sizeof(subs)); + + if (perm & PERM_WRITE) { + subs.sender = dp->addr; + subs.dest.client = mdev->client; + subs.dest.port = mdev->port; + if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0) + mdev->opened |= PERM_WRITE; + } + if (perm & PERM_READ) { + subs.sender.client = mdev->client; + subs.sender.port = mdev->port; + subs.dest = dp->addr; + subs.flags = SNDRV_SEQ_PORT_SUBS_TIMESTAMP; + subs.queue = dp->queue; /* queue for timestamps */ + if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0) + mdev->opened |= PERM_READ; + } + + if (! mdev->opened) { + snd_use_lock_free(&mdev->use_lock); + return -ENXIO; + } + + mdev->devinfo = dp; + snd_use_lock_free(&mdev->use_lock); + return 0; +} + +/* + * close the midi device if already opened + */ +int +snd_seq_oss_midi_close(seq_oss_devinfo_t *dp, int dev) +{ + seq_oss_midi_t *mdev; + snd_seq_port_subscribe_t subs; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return -ENODEV; + if (! mdev->opened || mdev->devinfo != dp) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } + + debug_printk(("closing client %d port %d mode %d\n", mdev->client, mdev->port, mdev->opened)); + memset(&subs, 0, sizeof(subs)); + if (mdev->opened & PERM_WRITE) { + subs.sender = dp->addr; + subs.dest.client = mdev->client; + subs.dest.port = mdev->port; + snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs); + } + if (mdev->opened & PERM_READ) { + subs.sender.client = mdev->client; + subs.sender.port = mdev->port; + subs.dest = dp->addr; + snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs); + } + + mdev->opened = 0; + mdev->devinfo = NULL; + + snd_use_lock_free(&mdev->use_lock); + return 0; +} + +/* + * change seq capability flags to file mode flags + */ +int +snd_seq_oss_midi_filemode(seq_oss_devinfo_t *dp, int dev) +{ + seq_oss_midi_t *mdev; + int mode; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return 0; + + mode = 0; + if (mdev->opened & PERM_WRITE) + mode |= SNDRV_SEQ_OSS_FILE_WRITE; + if (mdev->opened & PERM_READ) + mode |= SNDRV_SEQ_OSS_FILE_READ; + + snd_use_lock_free(&mdev->use_lock); + return mode; +} + +/* + * reset the midi device and close it: + * so far, only close the device. + */ +void +snd_seq_oss_midi_reset(seq_oss_devinfo_t *dp, int dev) +{ + seq_oss_midi_t *mdev; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return; + if (! mdev->opened) { + snd_use_lock_free(&mdev->use_lock); + return; + } + + if (mdev->opened & PERM_WRITE) { + snd_seq_event_t ev; + int c; + + debug_printk(("resetting client %d port %d\n", mdev->client, mdev->port)); + memset(&ev, 0, sizeof(ev)); + ev.dest.client = mdev->client; + ev.dest.port = mdev->port; + ev.queue = dp->queue; + ev.source.port = dp->port; + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) { + ev.type = SNDRV_SEQ_EVENT_SENSING; + snd_seq_oss_dispatch(dp, &ev, 0, 0); /* active sensing */ + } + for (c = 0; c < 16; c++) { + ev.type = SNDRV_SEQ_EVENT_CONTROLLER; + ev.data.control.channel = c; + ev.data.control.param = 123; + snd_seq_oss_dispatch(dp, &ev, 0, 0); /* all notes off */ + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { + ev.data.control.param = 121; + snd_seq_oss_dispatch(dp, &ev, 0, 0); /* reset all controllers */ + ev.type = SNDRV_SEQ_EVENT_PITCHBEND; + ev.data.control.value = 0; + snd_seq_oss_dispatch(dp, &ev, 0, 0); /* bender off */ + } + } + } + // snd_seq_oss_midi_close(dp, dev); + snd_use_lock_free(&mdev->use_lock); +} + + +/* + * get client/port of the specified MIDI device + */ +void +snd_seq_oss_midi_get_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_addr_t *addr) +{ + seq_oss_midi_t *mdev; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return; + addr->client = mdev->client; + addr->port = mdev->port; + snd_use_lock_free(&mdev->use_lock); +} + + +/* + * input callback - this can be atomic + */ +int +snd_seq_oss_midi_input(snd_seq_event_t *ev, int direct, void *private_data) +{ + seq_oss_devinfo_t *dp = (seq_oss_devinfo_t *)private_data; + seq_oss_midi_t *mdev; + int rc; + + if (dp->readq == NULL) + return 0; + if ((mdev = find_slot(ev->source.client, ev->source.port)) == NULL) + return 0; + if (! (mdev->opened & PERM_READ)) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } + + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + rc = send_synth_event(dp, ev, mdev->seq_device); + else + rc = send_midi_event(dp, ev, mdev); + + snd_use_lock_free(&mdev->use_lock); + return rc; +} + +/* + * convert ALSA sequencer event to OSS synth event + */ +static int +send_synth_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, int dev) +{ + evrec_t ossev; + + memset(&ossev, 0, sizeof(ossev)); + + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEON: + ossev.v.cmd = MIDI_NOTEON; break; + case SNDRV_SEQ_EVENT_NOTEOFF: + ossev.v.cmd = MIDI_NOTEOFF; break; + case SNDRV_SEQ_EVENT_KEYPRESS: + ossev.v.cmd = MIDI_KEY_PRESSURE; break; + case SNDRV_SEQ_EVENT_CONTROLLER: + ossev.l.cmd = MIDI_CTL_CHANGE; break; + case SNDRV_SEQ_EVENT_PGMCHANGE: + ossev.l.cmd = MIDI_PGM_CHANGE; break; + case SNDRV_SEQ_EVENT_CHANPRESS: + ossev.l.cmd = MIDI_CHN_PRESSURE; break; + case SNDRV_SEQ_EVENT_PITCHBEND: + ossev.l.cmd = MIDI_PITCH_BEND; break; + default: + return 0; /* not supported */ + } + + ossev.v.dev = dev; + + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEON: + case SNDRV_SEQ_EVENT_NOTEOFF: + case SNDRV_SEQ_EVENT_KEYPRESS: + ossev.v.code = EV_CHN_VOICE; + ossev.v.note = ev->data.note.note; + ossev.v.parm = ev->data.note.velocity; + ossev.v.chn = ev->data.note.channel; + break; + case SNDRV_SEQ_EVENT_CONTROLLER: + case SNDRV_SEQ_EVENT_PGMCHANGE: + case SNDRV_SEQ_EVENT_CHANPRESS: + ossev.l.code = EV_CHN_COMMON; + ossev.l.p1 = ev->data.control.param; + ossev.l.val = ev->data.control.value; + ossev.l.chn = ev->data.control.channel; + break; + case SNDRV_SEQ_EVENT_PITCHBEND: + ossev.l.code = EV_CHN_COMMON; + ossev.l.val = ev->data.control.value + 8192; + ossev.l.chn = ev->data.control.channel; + break; + } + + snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode); + snd_seq_oss_readq_put_event(dp->readq, &ossev); + + return 0; +} + +/* + * decode event and send MIDI bytes to read queue + */ +static int +send_midi_event(seq_oss_devinfo_t *dp, snd_seq_event_t *ev, seq_oss_midi_t *mdev) +{ + char msg[32]; + int len; + + snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode); + if (!dp->timer->running) + len = snd_seq_oss_timer_start(dp->timer); + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + snd_seq_oss_readq_puts(dp->readq, mdev->seq_device, + ev->data.ext.ptr, ev->data.ext.len); + } else { + len = snd_midi_event_decode(mdev->coder, msg, sizeof(msg), ev); + if (len > 0) + snd_seq_oss_readq_puts(dp->readq, mdev->seq_device, msg, len); + } + + return 0; +} + + +/* + * dump midi data + * return 0 : enqueued + * non-zero : invalid - ignored + */ +int +snd_seq_oss_midi_putc(seq_oss_devinfo_t *dp, int dev, unsigned char c, snd_seq_event_t *ev) +{ + seq_oss_midi_t *mdev; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return -ENODEV; + if (snd_midi_event_encode_byte(mdev->coder, c, ev) > 0) { + snd_seq_oss_fill_addr(dp, ev, mdev->client, mdev->port); + snd_use_lock_free(&mdev->use_lock); + return 0; + } + snd_use_lock_free(&mdev->use_lock); + return -EINVAL; +} + +/* + * create OSS compatible midi_info record + */ +int +snd_seq_oss_midi_make_info(seq_oss_devinfo_t *dp, int dev, struct midi_info *inf) +{ + seq_oss_midi_t *mdev; + + if ((mdev = get_mididev(dp, dev)) == NULL) + return -ENXIO; + inf->device = dev; + inf->dev_type = 0; /* FIXME: ?? */ + inf->capabilities = 0; /* FIXME: ?? */ + strlcpy(inf->name, mdev->name, sizeof(inf->name)); + snd_use_lock_free(&mdev->use_lock); + return 0; +} + + +/* + * proc interface + */ +static char * +capmode_str(int val) +{ + val &= PERM_READ|PERM_WRITE; + if (val == (PERM_READ|PERM_WRITE)) + return "read/write"; + else if (val == PERM_READ) + return "read"; + else if (val == PERM_WRITE) + return "write"; + else + return "none"; +} + +void +snd_seq_oss_midi_info_read(snd_info_buffer_t *buf) +{ + int i; + seq_oss_midi_t *mdev; + + snd_iprintf(buf, "\nNumber of MIDI devices: %d\n", max_midi_devs); + for (i = 0; i < max_midi_devs; i++) { + snd_iprintf(buf, "\nmidi %d: ", i); + mdev = get_mdev(i); + if (mdev == NULL) { + snd_iprintf(buf, "*empty*\n"); + continue; + } + snd_iprintf(buf, "[%s] ALSA port %d:%d\n", mdev->name, + mdev->client, mdev->port); + snd_iprintf(buf, " capability %s / opened %s\n", + capmode_str(mdev->flags), + capmode_str(mdev->opened)); + snd_use_lock_free(&mdev->use_lock); + } +} + diff --git a/sound/core/seq/oss/seq_oss_midi.h b/sound/core/seq/oss/seq_oss_midi.h new file mode 100644 index 000000000000..462484b2b6fe --- /dev/null +++ b/sound/core/seq/oss/seq_oss_midi.h @@ -0,0 +1,49 @@ +/* + * OSS compatible sequencer driver + * + * midi device information + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_MIDI_H +#define __SEQ_OSS_MIDI_H + +#include "seq_oss_device.h" +#include <sound/seq_oss_legacy.h> + +typedef struct seq_oss_midi_t seq_oss_midi_t; + +int snd_seq_oss_midi_lookup_ports(int client); +int snd_seq_oss_midi_check_new_port(snd_seq_port_info_t *pinfo); +int snd_seq_oss_midi_check_exit_port(int client, int port); +void snd_seq_oss_midi_clear_all(void); + +void snd_seq_oss_midi_setup(seq_oss_devinfo_t *dp); +void snd_seq_oss_midi_cleanup(seq_oss_devinfo_t *dp); + +int snd_seq_oss_midi_open(seq_oss_devinfo_t *dp, int dev, int file_mode); +void snd_seq_oss_midi_open_all(seq_oss_devinfo_t *dp, int file_mode); +int snd_seq_oss_midi_close(seq_oss_devinfo_t *dp, int dev); +void snd_seq_oss_midi_reset(seq_oss_devinfo_t *dp, int dev); +int snd_seq_oss_midi_putc(seq_oss_devinfo_t *dp, int dev, unsigned char c, snd_seq_event_t *ev); +int snd_seq_oss_midi_input(snd_seq_event_t *ev, int direct, void *private); +int snd_seq_oss_midi_filemode(seq_oss_devinfo_t *dp, int dev); +int snd_seq_oss_midi_make_info(seq_oss_devinfo_t *dp, int dev, struct midi_info *inf); +void snd_seq_oss_midi_get_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_addr_t *addr); + +#endif diff --git a/sound/core/seq/oss/seq_oss_readq.c b/sound/core/seq/oss/seq_oss_readq.c new file mode 100644 index 000000000000..0a6f2a64f692 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_readq.c @@ -0,0 +1,234 @@ +/* + * OSS compatible sequencer driver + * + * seq_oss_readq.c - MIDI input queue + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_readq.h" +#include "seq_oss_event.h" +#include <sound/seq_oss_legacy.h> +#include "../seq_lock.h" +#include <linux/wait.h> + +/* + * constants + */ +//#define SNDRV_SEQ_OSS_MAX_TIMEOUT (unsigned long)(-1) +#define SNDRV_SEQ_OSS_MAX_TIMEOUT (HZ * 3600) + + +/* + * prototypes + */ + + +/* + * create a read queue + */ +seq_oss_readq_t * +snd_seq_oss_readq_new(seq_oss_devinfo_t *dp, int maxlen) +{ + seq_oss_readq_t *q; + + if ((q = kcalloc(1, sizeof(*q), GFP_KERNEL)) == NULL) { + snd_printk(KERN_ERR "can't malloc read queue\n"); + return NULL; + } + + if ((q->q = kcalloc(maxlen, sizeof(evrec_t), GFP_KERNEL)) == NULL) { + snd_printk(KERN_ERR "can't malloc read queue buffer\n"); + kfree(q); + return NULL; + } + + q->maxlen = maxlen; + q->qlen = 0; + q->head = q->tail = 0; + init_waitqueue_head(&q->midi_sleep); + spin_lock_init(&q->lock); + q->pre_event_timeout = SNDRV_SEQ_OSS_MAX_TIMEOUT; + q->input_time = (unsigned long)-1; + + return q; +} + +/* + * delete the read queue + */ +void +snd_seq_oss_readq_delete(seq_oss_readq_t *q) +{ + if (q) { + kfree(q->q); + kfree(q); + } +} + +/* + * reset the read queue + */ +void +snd_seq_oss_readq_clear(seq_oss_readq_t *q) +{ + if (q->qlen) { + q->qlen = 0; + q->head = q->tail = 0; + } + /* if someone sleeping, wake'em up */ + if (waitqueue_active(&q->midi_sleep)) + wake_up(&q->midi_sleep); + q->input_time = (unsigned long)-1; +} + +/* + * put a midi byte + */ +int +snd_seq_oss_readq_puts(seq_oss_readq_t *q, int dev, unsigned char *data, int len) +{ + evrec_t rec; + int result; + + memset(&rec, 0, sizeof(rec)); + rec.c[0] = SEQ_MIDIPUTC; + rec.c[2] = dev; + + while (len-- > 0) { + rec.c[1] = *data++; + result = snd_seq_oss_readq_put_event(q, &rec); + if (result < 0) + return result; + } + return 0; +} + +/* + * copy an event to input queue: + * return zero if enqueued + */ +int +snd_seq_oss_readq_put_event(seq_oss_readq_t *q, evrec_t *ev) +{ + unsigned long flags; + + spin_lock_irqsave(&q->lock, flags); + if (q->qlen >= q->maxlen - 1) { + spin_unlock_irqrestore(&q->lock, flags); + return -ENOMEM; + } + + memcpy(&q->q[q->tail], ev, sizeof(*ev)); + q->tail = (q->tail + 1) % q->maxlen; + q->qlen++; + + /* wake up sleeper */ + if (waitqueue_active(&q->midi_sleep)) + wake_up(&q->midi_sleep); + + spin_unlock_irqrestore(&q->lock, flags); + + return 0; +} + + +/* + * pop queue + * caller must hold lock + */ +int +snd_seq_oss_readq_pick(seq_oss_readq_t *q, evrec_t *rec) +{ + if (q->qlen == 0) + return -EAGAIN; + memcpy(rec, &q->q[q->head], sizeof(*rec)); + return 0; +} + +/* + * sleep until ready + */ +void +snd_seq_oss_readq_wait(seq_oss_readq_t *q) +{ + wait_event_interruptible_timeout(q->midi_sleep, + (q->qlen > 0 || q->head == q->tail), + q->pre_event_timeout); +} + +/* + * drain one record + * caller must hold lock + */ +void +snd_seq_oss_readq_free(seq_oss_readq_t *q) +{ + if (q->qlen > 0) { + q->head = (q->head + 1) % q->maxlen; + q->qlen--; + } +} + +/* + * polling/select: + * return non-zero if readq is not empty. + */ +unsigned int +snd_seq_oss_readq_poll(seq_oss_readq_t *q, struct file *file, poll_table *wait) +{ + poll_wait(file, &q->midi_sleep, wait); + return q->qlen; +} + +/* + * put a timestamp + */ +int +snd_seq_oss_readq_put_timestamp(seq_oss_readq_t *q, unsigned long curt, int seq_mode) +{ + if (curt != q->input_time) { + evrec_t rec; + memset(&rec, 0, sizeof(rec)); + switch (seq_mode) { + case SNDRV_SEQ_OSS_MODE_SYNTH: + rec.echo = (curt << 8) | SEQ_WAIT; + snd_seq_oss_readq_put_event(q, &rec); + break; + case SNDRV_SEQ_OSS_MODE_MUSIC: + rec.t.code = EV_TIMING; + rec.t.cmd = TMR_WAIT_ABS; + rec.t.time = curt; + snd_seq_oss_readq_put_event(q, &rec); + break; + } + q->input_time = curt; + } + return 0; +} + + +/* + * proc interface + */ +void +snd_seq_oss_readq_info_read(seq_oss_readq_t *q, snd_info_buffer_t *buf) +{ + snd_iprintf(buf, " read queue [%s] length = %d : tick = %ld\n", + (waitqueue_active(&q->midi_sleep) ? "sleeping":"running"), + q->qlen, q->input_time); +} diff --git a/sound/core/seq/oss/seq_oss_readq.h b/sound/core/seq/oss/seq_oss_readq.h new file mode 100644 index 000000000000..303b9298f206 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_readq.h @@ -0,0 +1,56 @@ +/* + * OSS compatible sequencer driver + * read fifo queue + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_READQ_H +#define __SEQ_OSS_READQ_H + +#include "seq_oss_device.h" + + +/* + * definition of read queue + */ +struct seq_oss_readq_t { + evrec_t *q; + int qlen; + int maxlen; + int head, tail; + unsigned long pre_event_timeout; + unsigned long input_time; + wait_queue_head_t midi_sleep; + spinlock_t lock; +}; + +seq_oss_readq_t *snd_seq_oss_readq_new(seq_oss_devinfo_t *dp, int maxlen); +void snd_seq_oss_readq_delete(seq_oss_readq_t *q); +void snd_seq_oss_readq_clear(seq_oss_readq_t *readq); +unsigned int snd_seq_oss_readq_poll(seq_oss_readq_t *readq, struct file *file, poll_table *wait); +int snd_seq_oss_readq_puts(seq_oss_readq_t *readq, int dev, unsigned char *data, int len); +int snd_seq_oss_readq_put_event(seq_oss_readq_t *readq, evrec_t *ev); +int snd_seq_oss_readq_put_timestamp(seq_oss_readq_t *readq, unsigned long curt, int seq_mode); +int snd_seq_oss_readq_pick(seq_oss_readq_t *q, evrec_t *rec); +void snd_seq_oss_readq_wait(seq_oss_readq_t *q); +void snd_seq_oss_readq_free(seq_oss_readq_t *q); + +#define snd_seq_oss_readq_lock(q, flags) spin_lock_irqsave(&(q)->lock, flags) +#define snd_seq_oss_readq_unlock(q, flags) spin_unlock_irqrestore(&(q)->lock, flags) + +#endif diff --git a/sound/core/seq/oss/seq_oss_rw.c b/sound/core/seq/oss/seq_oss_rw.c new file mode 100644 index 000000000000..1d8fbd22e3e3 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_rw.c @@ -0,0 +1,216 @@ +/* + * OSS compatible sequencer driver + * + * read/write/select interface to device file + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_device.h" +#include "seq_oss_readq.h" +#include "seq_oss_writeq.h" +#include "seq_oss_synth.h" +#include <sound/seq_oss_legacy.h> +#include "seq_oss_event.h" +#include "seq_oss_timer.h" +#include "../seq_clientmgr.h" + + +/* + * protoypes + */ +static int insert_queue(seq_oss_devinfo_t *dp, evrec_t *rec, struct file *opt); + + +/* + * read interface + */ + +int +snd_seq_oss_read(seq_oss_devinfo_t *dp, char __user *buf, int count) +{ + seq_oss_readq_t *readq = dp->readq; + int result = 0, err = 0; + int ev_len; + evrec_t rec; + unsigned long flags; + + if (readq == NULL || ! is_read_mode(dp->file_mode)) + return -ENXIO; + + while (count >= SHORT_EVENT_SIZE) { + snd_seq_oss_readq_lock(readq, flags); + err = snd_seq_oss_readq_pick(readq, &rec); + if (err == -EAGAIN && + !is_nonblock_mode(dp->file_mode) && result == 0) { + snd_seq_oss_readq_unlock(readq, flags); + snd_seq_oss_readq_wait(readq); + snd_seq_oss_readq_lock(readq, flags); + if (signal_pending(current)) + err = -ERESTARTSYS; + else + err = snd_seq_oss_readq_pick(readq, &rec); + } + if (err < 0) { + snd_seq_oss_readq_unlock(readq, flags); + break; + } + ev_len = ev_length(&rec); + if (ev_len < count) { + snd_seq_oss_readq_unlock(readq, flags); + break; + } + snd_seq_oss_readq_free(readq); + snd_seq_oss_readq_unlock(readq, flags); + if (copy_to_user(buf, &rec, ev_len)) { + err = -EFAULT; + break; + } + result += ev_len; + buf += ev_len; + count -= ev_len; + } + return result > 0 ? result : err; +} + + +/* + * write interface + */ + +int +snd_seq_oss_write(seq_oss_devinfo_t *dp, const char __user *buf, int count, struct file *opt) +{ + int result = 0, err = 0; + int ev_size, fmt; + evrec_t rec; + + if (! is_write_mode(dp->file_mode) || dp->writeq == NULL) + return -ENXIO; + + while (count >= SHORT_EVENT_SIZE) { + if (copy_from_user(&rec, buf, SHORT_EVENT_SIZE)) { + err = -EFAULT; + break; + } + if (rec.s.code == SEQ_FULLSIZE) { + /* load patch */ + if (result > 0) { + err = -EINVAL; + break; + } + fmt = (*(unsigned short *)rec.c) & 0xffff; + /* FIXME the return value isn't correct */ + return snd_seq_oss_synth_load_patch(dp, rec.s.dev, + fmt, buf, 0, count); + } + if (ev_is_long(&rec)) { + /* extended code */ + if (rec.s.code == SEQ_EXTENDED && + dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { + err = -EINVAL; + break; + } + ev_size = LONG_EVENT_SIZE; + if (count < ev_size) + break; + /* copy the reset 4 bytes */ + if (copy_from_user(rec.c + SHORT_EVENT_SIZE, + buf + SHORT_EVENT_SIZE, + LONG_EVENT_SIZE - SHORT_EVENT_SIZE)) { + err = -EFAULT; + break; + } + } else { + /* old-type code */ + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { + err = -EINVAL; + break; + } + ev_size = SHORT_EVENT_SIZE; + } + + /* insert queue */ + if ((err = insert_queue(dp, &rec, opt)) < 0) + break; + + result += ev_size; + buf += ev_size; + count -= ev_size; + } + return result > 0 ? result : err; +} + + +/* + * insert event record to write queue + * return: 0 = OK, non-zero = NG + */ +static int +insert_queue(seq_oss_devinfo_t *dp, evrec_t *rec, struct file *opt) +{ + int rc = 0; + snd_seq_event_t event; + + /* if this is a timing event, process the current time */ + if (snd_seq_oss_process_timer_event(dp->timer, rec)) + return 0; /* no need to insert queue */ + + /* parse this event */ + memset(&event, 0, sizeof(event)); + /* set dummy -- to be sure */ + event.type = SNDRV_SEQ_EVENT_NOTEOFF; + snd_seq_oss_fill_addr(dp, &event, dp->addr.port, dp->addr.client); + + if (snd_seq_oss_process_event(dp, rec, &event)) + return 0; /* invalid event - no need to insert queue */ + + event.time.tick = snd_seq_oss_timer_cur_tick(dp->timer); + if (dp->timer->realtime || !dp->timer->running) { + snd_seq_oss_dispatch(dp, &event, 0, 0); + } else { + if (is_nonblock_mode(dp->file_mode)) + rc = snd_seq_kernel_client_enqueue(dp->cseq, &event, 0, 0); + else + rc = snd_seq_kernel_client_enqueue_blocking(dp->cseq, &event, opt, 0, 0); + } + return rc; +} + + +/* + * select / poll + */ + +unsigned int +snd_seq_oss_poll(seq_oss_devinfo_t *dp, struct file *file, poll_table * wait) +{ + unsigned int mask = 0; + + /* input */ + if (dp->readq && is_read_mode(dp->file_mode)) { + if (snd_seq_oss_readq_poll(dp->readq, file, wait)) + mask |= POLLIN | POLLRDNORM; + } + + /* output */ + if (dp->writeq && is_write_mode(dp->file_mode)) { + if (snd_seq_kernel_client_write_poll(dp->cseq, file, wait)) + mask |= POLLOUT | POLLWRNORM; + } + return mask; +} diff --git a/sound/core/seq/oss/seq_oss_synth.c b/sound/core/seq/oss/seq_oss_synth.c new file mode 100644 index 000000000000..638cc148706d --- /dev/null +++ b/sound/core/seq/oss/seq_oss_synth.c @@ -0,0 +1,659 @@ +/* + * OSS compatible sequencer driver + * + * synth device handlers + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "../seq_lock.h" +#include <linux/init.h> + +/* + * constants + */ +#define SNDRV_SEQ_OSS_MAX_SYNTH_NAME 30 +#define MAX_SYSEX_BUFLEN 128 + + +/* + * definition of synth info records + */ + +/* sysex buffer */ +struct seq_oss_synth_sysex_t { + int len; + int skip; + unsigned char buf[MAX_SYSEX_BUFLEN]; +}; + +/* synth info */ +struct seq_oss_synth_t { + int seq_device; + + /* for synth_info */ + int synth_type; + int synth_subtype; + int nr_voices; + + char name[SNDRV_SEQ_OSS_MAX_SYNTH_NAME]; + snd_seq_oss_callback_t oper; + + int opened; + + void *private_data; + snd_use_lock_t use_lock; +}; + + +/* + * device table + */ +static int max_synth_devs; +static seq_oss_synth_t *synth_devs[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS]; +static seq_oss_synth_t midi_synth_dev = { + -1, /* seq_device */ + SYNTH_TYPE_MIDI, /* synth_type */ + 0, /* synth_subtype */ + 16, /* nr_voices */ + "MIDI", /* name */ +}; + +static DEFINE_SPINLOCK(register_lock); + +/* + * prototypes + */ +static seq_oss_synth_t *get_synthdev(seq_oss_devinfo_t *dp, int dev); +static void reset_channels(seq_oss_synthinfo_t *info); + +/* + * global initialization + */ +void __init +snd_seq_oss_synth_init(void) +{ + snd_use_lock_init(&midi_synth_dev.use_lock); +} + +/* + * registration of the synth device + */ +int +snd_seq_oss_synth_register(snd_seq_device_t *dev) +{ + int i; + seq_oss_synth_t *rec; + snd_seq_oss_reg_t *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev); + unsigned long flags; + + if ((rec = kcalloc(1, sizeof(*rec), GFP_KERNEL)) == NULL) { + snd_printk(KERN_ERR "can't malloc synth info\n"); + return -ENOMEM; + } + rec->seq_device = -1; + rec->synth_type = reg->type; + rec->synth_subtype = reg->subtype; + rec->nr_voices = reg->nvoices; + rec->oper = reg->oper; + rec->private_data = reg->private_data; + rec->opened = 0; + snd_use_lock_init(&rec->use_lock); + + /* copy and truncate the name of synth device */ + strlcpy(rec->name, dev->name, sizeof(rec->name)); + + /* registration */ + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_synth_devs; i++) { + if (synth_devs[i] == NULL) + break; + } + if (i >= max_synth_devs) { + if (max_synth_devs >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) { + spin_unlock_irqrestore(®ister_lock, flags); + snd_printk(KERN_ERR "no more synth slot\n"); + kfree(rec); + return -ENOMEM; + } + max_synth_devs++; + } + rec->seq_device = i; + synth_devs[i] = rec; + debug_printk(("synth %s registered %d\n", rec->name, i)); + spin_unlock_irqrestore(®ister_lock, flags); + dev->driver_data = rec; +#ifdef SNDRV_OSS_INFO_DEV_SYNTH + if (i < SNDRV_CARDS) + snd_oss_info_register(SNDRV_OSS_INFO_DEV_SYNTH, i, rec->name); +#endif + return 0; +} + + +int +snd_seq_oss_synth_unregister(snd_seq_device_t *dev) +{ + int index; + seq_oss_synth_t *rec = dev->driver_data; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + for (index = 0; index < max_synth_devs; index++) { + if (synth_devs[index] == rec) + break; + } + if (index >= max_synth_devs) { + spin_unlock_irqrestore(®ister_lock, flags); + snd_printk(KERN_ERR "can't unregister synth\n"); + return -EINVAL; + } + synth_devs[index] = NULL; + if (index == max_synth_devs - 1) { + for (index--; index >= 0; index--) { + if (synth_devs[index]) + break; + } + max_synth_devs = index + 1; + } + spin_unlock_irqrestore(®ister_lock, flags); +#ifdef SNDRV_OSS_INFO_DEV_SYNTH + if (rec->seq_device < SNDRV_CARDS) + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_SYNTH, rec->seq_device); +#endif + + snd_use_lock_sync(&rec->use_lock); + kfree(rec); + + return 0; +} + + +/* + */ +static seq_oss_synth_t * +get_sdev(int dev) +{ + seq_oss_synth_t *rec; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + rec = synth_devs[dev]; + if (rec) + snd_use_lock_use(&rec->use_lock); + spin_unlock_irqrestore(®ister_lock, flags); + return rec; +} + + +/* + * set up synth tables + */ + +void +snd_seq_oss_synth_setup(seq_oss_devinfo_t *dp) +{ + int i; + seq_oss_synth_t *rec; + seq_oss_synthinfo_t *info; + + dp->max_synthdev = max_synth_devs; + dp->synth_opened = 0; + memset(dp->synths, 0, sizeof(dp->synths)); + for (i = 0; i < dp->max_synthdev; i++) { + rec = get_sdev(i); + if (rec == NULL) + continue; + if (rec->oper.open == NULL || rec->oper.close == NULL) { + snd_use_lock_free(&rec->use_lock); + continue; + } + info = &dp->synths[i]; + info->arg.app_index = dp->port; + info->arg.file_mode = dp->file_mode; + info->arg.seq_mode = dp->seq_mode; + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) + info->arg.event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; + else + info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS; + info->opened = 0; + if (!try_module_get(rec->oper.owner)) { + snd_use_lock_free(&rec->use_lock); + continue; + } + if (rec->oper.open(&info->arg, rec->private_data) < 0) { + module_put(rec->oper.owner); + snd_use_lock_free(&rec->use_lock); + continue; + } + info->nr_voices = rec->nr_voices; + if (info->nr_voices > 0) { + info->ch = kcalloc(info->nr_voices, sizeof(seq_oss_chinfo_t), GFP_KERNEL); + if (!info->ch) + BUG(); + reset_channels(info); + } + debug_printk(("synth %d assigned\n", i)); + info->opened++; + rec->opened++; + dp->synth_opened++; + snd_use_lock_free(&rec->use_lock); + } +} + + +/* + * set up synth tables for MIDI emulation - /dev/music mode only + */ + +void +snd_seq_oss_synth_setup_midi(seq_oss_devinfo_t *dp) +{ + int i; + + if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) + return; + + for (i = 0; i < dp->max_mididev; i++) { + seq_oss_synthinfo_t *info; + info = &dp->synths[dp->max_synthdev]; + if (snd_seq_oss_midi_open(dp, i, dp->file_mode) < 0) + continue; + info->arg.app_index = dp->port; + info->arg.file_mode = dp->file_mode; + info->arg.seq_mode = dp->seq_mode; + info->arg.private_data = info; + info->is_midi = 1; + info->midi_mapped = i; + info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS; + snd_seq_oss_midi_get_addr(dp, i, &info->arg.addr); + info->opened = 1; + midi_synth_dev.opened++; + dp->max_synthdev++; + if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) + break; + } +} + + +/* + * clean up synth tables + */ + +void +snd_seq_oss_synth_cleanup(seq_oss_devinfo_t *dp) +{ + int i; + seq_oss_synth_t *rec; + seq_oss_synthinfo_t *info; + + snd_assert(dp->max_synthdev <= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS, return); + for (i = 0; i < dp->max_synthdev; i++) { + info = &dp->synths[i]; + if (! info->opened) + continue; + if (info->is_midi) { + if (midi_synth_dev.opened > 0) { + snd_seq_oss_midi_close(dp, info->midi_mapped); + midi_synth_dev.opened--; + } + } else { + rec = get_sdev(i); + if (rec == NULL) + continue; + if (rec->opened > 0) { + debug_printk(("synth %d closed\n", i)); + rec->oper.close(&info->arg); + module_put(rec->oper.owner); + rec->opened = 0; + } + snd_use_lock_free(&rec->use_lock); + } + if (info->sysex) { + kfree(info->sysex); + info->sysex = NULL; + } + if (info->ch) { + kfree(info->ch); + info->ch = NULL; + } + } + dp->synth_opened = 0; + dp->max_synthdev = 0; +} + +/* + * check if the specified device is MIDI mapped device + */ +static int +is_midi_dev(seq_oss_devinfo_t *dp, int dev) +{ + if (dev < 0 || dev >= dp->max_synthdev) + return 0; + if (dp->synths[dev].is_midi) + return 1; + return 0; +} + +/* + * return synth device information pointer + */ +static seq_oss_synth_t * +get_synthdev(seq_oss_devinfo_t *dp, int dev) +{ + seq_oss_synth_t *rec; + if (dev < 0 || dev >= dp->max_synthdev) + return NULL; + if (! dp->synths[dev].opened) + return NULL; + if (dp->synths[dev].is_midi) + return &midi_synth_dev; + if ((rec = get_sdev(dev)) == NULL) + return NULL; + if (! rec->opened) { + snd_use_lock_free(&rec->use_lock); + return NULL; + } + return rec; +} + + +/* + * reset note and velocity on each channel. + */ +static void +reset_channels(seq_oss_synthinfo_t *info) +{ + int i; + if (info->ch == NULL || ! info->nr_voices) + return; + for (i = 0; i < info->nr_voices; i++) { + info->ch[i].note = -1; + info->ch[i].vel = 0; + } +} + + +/* + * reset synth device: + * call reset callback. if no callback is defined, send a heartbeat + * event to the corresponding port. + */ +void +snd_seq_oss_synth_reset(seq_oss_devinfo_t *dp, int dev) +{ + seq_oss_synth_t *rec; + seq_oss_synthinfo_t *info; + + snd_assert(dev >= 0 && dev < dp->max_synthdev, return); + info = &dp->synths[dev]; + if (! info->opened) + return; + if (info->sysex) + info->sysex->len = 0; /* reset sysex */ + reset_channels(info); + if (info->is_midi) { + if (midi_synth_dev.opened <= 0) + return; + snd_seq_oss_midi_reset(dp, info->midi_mapped); + /* reopen the device */ + snd_seq_oss_midi_close(dp, dev); + if (snd_seq_oss_midi_open(dp, info->midi_mapped, + dp->file_mode) < 0) { + midi_synth_dev.opened--; + info->opened = 0; + if (info->sysex) { + kfree(info->sysex); + info->sysex = NULL; + } + if (info->ch) { + kfree(info->ch); + info->ch = NULL; + } + } + return; + } + + rec = get_sdev(dev); + if (rec == NULL) + return; + if (rec->oper.reset) { + rec->oper.reset(&info->arg); + } else { + snd_seq_event_t ev; + memset(&ev, 0, sizeof(ev)); + snd_seq_oss_fill_addr(dp, &ev, info->arg.addr.client, + info->arg.addr.port); + ev.type = SNDRV_SEQ_EVENT_RESET; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + } + snd_use_lock_free(&rec->use_lock); +} + + +/* + * load a patch record: + * call load_patch callback function + */ +int +snd_seq_oss_synth_load_patch(seq_oss_devinfo_t *dp, int dev, int fmt, + const char __user *buf, int p, int c) +{ + seq_oss_synth_t *rec; + int rc; + + if (dev < 0 || dev >= dp->max_synthdev) + return -ENXIO; + + if (is_midi_dev(dp, dev)) + return 0; + if ((rec = get_synthdev(dp, dev)) == NULL) + return -ENXIO; + + if (rec->oper.load_patch == NULL) + rc = -ENXIO; + else + rc = rec->oper.load_patch(&dp->synths[dev].arg, fmt, buf, p, c); + snd_use_lock_free(&rec->use_lock); + return rc; +} + +/* + * check if the device is valid synth device + */ +int +snd_seq_oss_synth_is_valid(seq_oss_devinfo_t *dp, int dev) +{ + seq_oss_synth_t *rec; + rec = get_synthdev(dp, dev); + if (rec) { + snd_use_lock_free(&rec->use_lock); + return 1; + } + return 0; +} + + +/* + * receive OSS 6 byte sysex packet: + * the full sysex message will be sent if it reaches to the end of data + * (0xff). + */ +int +snd_seq_oss_synth_sysex(seq_oss_devinfo_t *dp, int dev, unsigned char *buf, snd_seq_event_t *ev) +{ + int i, send; + unsigned char *dest; + seq_oss_synth_sysex_t *sysex; + + if (! snd_seq_oss_synth_is_valid(dp, dev)) + return -ENXIO; + + sysex = dp->synths[dev].sysex; + if (sysex == NULL) { + sysex = kcalloc(1, sizeof(*sysex), GFP_KERNEL); + if (sysex == NULL) + return -ENOMEM; + dp->synths[dev].sysex = sysex; + } + + send = 0; + dest = sysex->buf + sysex->len; + /* copy 6 byte packet to the buffer */ + for (i = 0; i < 6; i++) { + if (buf[i] == 0xff) { + send = 1; + break; + } + dest[i] = buf[i]; + sysex->len++; + if (sysex->len >= MAX_SYSEX_BUFLEN) { + sysex->len = 0; + sysex->skip = 1; + break; + } + } + + if (sysex->len && send) { + if (sysex->skip) { + sysex->skip = 0; + sysex->len = 0; + return -EINVAL; /* skip */ + } + /* copy the data to event record and send it */ + ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + if (snd_seq_oss_synth_addr(dp, dev, ev)) + return -EINVAL; + ev->data.ext.len = sysex->len; + ev->data.ext.ptr = sysex->buf; + sysex->len = 0; + return 0; + } + + return -EINVAL; /* skip */ +} + +/* + * fill the event source/destination addresses + */ +int +snd_seq_oss_synth_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_event_t *ev) +{ + if (! snd_seq_oss_synth_is_valid(dp, dev)) + return -EINVAL; + snd_seq_oss_fill_addr(dp, ev, dp->synths[dev].arg.addr.client, + dp->synths[dev].arg.addr.port); + return 0; +} + + +/* + * OSS compatible ioctl + */ +int +snd_seq_oss_synth_ioctl(seq_oss_devinfo_t *dp, int dev, unsigned int cmd, unsigned long addr) +{ + seq_oss_synth_t *rec; + int rc; + + if (is_midi_dev(dp, dev)) + return -ENXIO; + if ((rec = get_synthdev(dp, dev)) == NULL) + return -ENXIO; + if (rec->oper.ioctl == NULL) + rc = -ENXIO; + else + rc = rec->oper.ioctl(&dp->synths[dev].arg, cmd, addr); + snd_use_lock_free(&rec->use_lock); + return rc; +} + + +/* + * send OSS raw events - SEQ_PRIVATE and SEQ_VOLUME + */ +int +snd_seq_oss_synth_raw_event(seq_oss_devinfo_t *dp, int dev, unsigned char *data, snd_seq_event_t *ev) +{ + if (! snd_seq_oss_synth_is_valid(dp, dev) || is_midi_dev(dp, dev)) + return -ENXIO; + ev->type = SNDRV_SEQ_EVENT_OSS; + memcpy(ev->data.raw8.d, data, 8); + return snd_seq_oss_synth_addr(dp, dev, ev); +} + + +/* + * create OSS compatible synth_info record + */ +int +snd_seq_oss_synth_make_info(seq_oss_devinfo_t *dp, int dev, struct synth_info *inf) +{ + seq_oss_synth_t *rec; + + if (dp->synths[dev].is_midi) { + struct midi_info minf; + snd_seq_oss_midi_make_info(dp, dp->synths[dev].midi_mapped, &minf); + inf->synth_type = SYNTH_TYPE_MIDI; + inf->synth_subtype = 0; + inf->nr_voices = 16; + inf->device = dev; + strlcpy(inf->name, minf.name, sizeof(inf->name)); + } else { + if ((rec = get_synthdev(dp, dev)) == NULL) + return -ENXIO; + inf->synth_type = rec->synth_type; + inf->synth_subtype = rec->synth_subtype; + inf->nr_voices = rec->nr_voices; + inf->device = dev; + strlcpy(inf->name, rec->name, sizeof(inf->name)); + snd_use_lock_free(&rec->use_lock); + } + return 0; +} + + +/* + * proc interface + */ +void +snd_seq_oss_synth_info_read(snd_info_buffer_t *buf) +{ + int i; + seq_oss_synth_t *rec; + + snd_iprintf(buf, "\nNumber of synth devices: %d\n", max_synth_devs); + for (i = 0; i < max_synth_devs; i++) { + snd_iprintf(buf, "\nsynth %d: ", i); + rec = get_sdev(i); + if (rec == NULL) { + snd_iprintf(buf, "*empty*\n"); + continue; + } + snd_iprintf(buf, "[%s]\n", rec->name); + snd_iprintf(buf, " type 0x%x : subtype 0x%x : voices %d\n", + rec->synth_type, rec->synth_subtype, + rec->nr_voices); + snd_iprintf(buf, " capabilities : ioctl %s / load_patch %s\n", + enabled_str((long)rec->oper.ioctl), + enabled_str((long)rec->oper.load_patch)); + snd_use_lock_free(&rec->use_lock); + } +} + diff --git a/sound/core/seq/oss/seq_oss_synth.h b/sound/core/seq/oss/seq_oss_synth.h new file mode 100644 index 000000000000..07bc0e2cfb82 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_synth.h @@ -0,0 +1,49 @@ +/* + * OSS compatible sequencer driver + * + * synth device information + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_SYNTH_H +#define __SEQ_OSS_SYNTH_H + +#include "seq_oss_device.h" +#include <sound/seq_oss_legacy.h> +#include <sound/seq_device.h> + +typedef struct seq_oss_synth_t seq_oss_synth_t; + +void snd_seq_oss_synth_init(void); +int snd_seq_oss_synth_register(snd_seq_device_t *dev); +int snd_seq_oss_synth_unregister(snd_seq_device_t *dev); +void snd_seq_oss_synth_setup(seq_oss_devinfo_t *dp); +void snd_seq_oss_synth_setup_midi(seq_oss_devinfo_t *dp); +void snd_seq_oss_synth_cleanup(seq_oss_devinfo_t *dp); + +void snd_seq_oss_synth_reset(seq_oss_devinfo_t *dp, int dev); +int snd_seq_oss_synth_load_patch(seq_oss_devinfo_t *dp, int dev, int fmt, const char __user *buf, int p, int c); +int snd_seq_oss_synth_is_valid(seq_oss_devinfo_t *dp, int dev); +int snd_seq_oss_synth_sysex(seq_oss_devinfo_t *dp, int dev, unsigned char *buf, snd_seq_event_t *ev); +int snd_seq_oss_synth_addr(seq_oss_devinfo_t *dp, int dev, snd_seq_event_t *ev); +int snd_seq_oss_synth_ioctl(seq_oss_devinfo_t *dp, int dev, unsigned int cmd, unsigned long addr); +int snd_seq_oss_synth_raw_event(seq_oss_devinfo_t *dp, int dev, unsigned char *data, snd_seq_event_t *ev); + +int snd_seq_oss_synth_make_info(seq_oss_devinfo_t *dp, int dev, struct synth_info *inf); + +#endif diff --git a/sound/core/seq/oss/seq_oss_timer.c b/sound/core/seq/oss/seq_oss_timer.c new file mode 100644 index 000000000000..42ca9493fa60 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_timer.c @@ -0,0 +1,283 @@ +/* + * OSS compatible sequencer driver + * + * Timer control routines + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_timer.h" +#include "seq_oss_event.h" +#include <sound/seq_oss_legacy.h> + +/* + */ +#define MIN_OSS_TEMPO 8 +#define MAX_OSS_TEMPO 360 +#define MIN_OSS_TIMEBASE 1 +#define MAX_OSS_TIMEBASE 1000 + +/* + */ +static void calc_alsa_tempo(seq_oss_timer_t *timer); +static int send_timer_event(seq_oss_devinfo_t *dp, int type, int value); + + +/* + * create and register a new timer. + * if queue is not started yet, start it. + */ +seq_oss_timer_t * +snd_seq_oss_timer_new(seq_oss_devinfo_t *dp) +{ + seq_oss_timer_t *rec; + + rec = kcalloc(1, sizeof(*rec), GFP_KERNEL); + if (rec == NULL) + return NULL; + + rec->dp = dp; + rec->cur_tick = 0; + rec->realtime = 0; + rec->running = 0; + rec->oss_tempo = 60; + rec->oss_timebase = 100; + calc_alsa_tempo(rec); + + return rec; +} + + +/* + * delete timer. + * if no more timer exists, stop the queue. + */ +void +snd_seq_oss_timer_delete(seq_oss_timer_t *rec) +{ + if (rec) { + snd_seq_oss_timer_stop(rec); + kfree(rec); + } +} + + +/* + * process one timing event + * return 1 : event proceseed -- skip this event + * 0 : not a timer event -- enqueue this event + */ +int +snd_seq_oss_process_timer_event(seq_oss_timer_t *rec, evrec_t *ev) +{ + abstime_t parm = ev->t.time; + + if (ev->t.code == EV_TIMING) { + switch (ev->t.cmd) { + case TMR_WAIT_REL: + parm += rec->cur_tick; + rec->realtime = 0; + /* continue to next */ + case TMR_WAIT_ABS: + if (parm == 0) { + rec->realtime = 1; + } else if (parm >= rec->cur_tick) { + rec->realtime = 0; + rec->cur_tick = parm; + } + return 1; /* skip this event */ + + case TMR_START: + snd_seq_oss_timer_start(rec); + return 1; + + } + } else if (ev->s.code == SEQ_WAIT) { + /* time = from 1 to 3 bytes */ + parm = (ev->echo >> 8) & 0xffffff; + if (parm > rec->cur_tick) { + /* set next event time */ + rec->cur_tick = parm; + rec->realtime = 0; + } + return 1; + } + + return 0; +} + + +/* + * convert tempo units + */ +static void +calc_alsa_tempo(seq_oss_timer_t *timer) +{ + timer->tempo = (60 * 1000000) / timer->oss_tempo; + timer->ppq = timer->oss_timebase; +} + + +/* + * dispatch a timer event + */ +static int +send_timer_event(seq_oss_devinfo_t *dp, int type, int value) +{ + snd_seq_event_t ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = type; + ev.source.client = dp->cseq; + ev.source.port = 0; + ev.dest.client = SNDRV_SEQ_CLIENT_SYSTEM; + ev.dest.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + ev.queue = dp->queue; + ev.data.queue.queue = dp->queue; + ev.data.queue.param.value = value; + return snd_seq_kernel_client_dispatch(dp->cseq, &ev, 1, 0); +} + +/* + * set queue tempo and start queue + */ +int +snd_seq_oss_timer_start(seq_oss_timer_t *timer) +{ + seq_oss_devinfo_t *dp = timer->dp; + snd_seq_queue_tempo_t tmprec; + + if (timer->running) + snd_seq_oss_timer_stop(timer); + + memset(&tmprec, 0, sizeof(tmprec)); + tmprec.queue = dp->queue; + tmprec.ppq = timer->ppq; + tmprec.tempo = timer->tempo; + snd_seq_set_queue_tempo(dp->cseq, &tmprec); + + send_timer_event(dp, SNDRV_SEQ_EVENT_START, 0); + timer->running = 1; + timer->cur_tick = 0; + return 0; +} + + +/* + * stop queue + */ +int +snd_seq_oss_timer_stop(seq_oss_timer_t *timer) +{ + if (! timer->running) + return 0; + send_timer_event(timer->dp, SNDRV_SEQ_EVENT_STOP, 0); + timer->running = 0; + return 0; +} + + +/* + * continue queue + */ +int +snd_seq_oss_timer_continue(seq_oss_timer_t *timer) +{ + if (timer->running) + return 0; + send_timer_event(timer->dp, SNDRV_SEQ_EVENT_CONTINUE, 0); + timer->running = 1; + return 0; +} + + +/* + * change queue tempo + */ +int +snd_seq_oss_timer_tempo(seq_oss_timer_t *timer, int value) +{ + if (value < MIN_OSS_TEMPO) + value = MIN_OSS_TEMPO; + else if (value > MAX_OSS_TEMPO) + value = MAX_OSS_TEMPO; + timer->oss_tempo = value; + calc_alsa_tempo(timer); + if (timer->running) + send_timer_event(timer->dp, SNDRV_SEQ_EVENT_TEMPO, timer->tempo); + return 0; +} + + +/* + * ioctls + */ +int +snd_seq_oss_timer_ioctl(seq_oss_timer_t *timer, unsigned int cmd, int __user *arg) +{ + int value; + + if (cmd == SNDCTL_SEQ_CTRLRATE) { + debug_printk(("ctrl rate\n")); + /* if *arg == 0, just return the current rate */ + if (get_user(value, arg)) + return -EFAULT; + if (value) + return -EINVAL; + value = ((timer->oss_tempo * timer->oss_timebase) + 30) / 60; + return put_user(value, arg) ? -EFAULT : 0; + } + + if (timer->dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) + return 0; + + switch (cmd) { + case SNDCTL_TMR_START: + debug_printk(("timer start\n")); + return snd_seq_oss_timer_start(timer); + case SNDCTL_TMR_STOP: + debug_printk(("timer stop\n")); + return snd_seq_oss_timer_stop(timer); + case SNDCTL_TMR_CONTINUE: + debug_printk(("timer continue\n")); + return snd_seq_oss_timer_continue(timer); + case SNDCTL_TMR_TEMPO: + debug_printk(("timer tempo\n")); + if (get_user(value, arg)) + return -EFAULT; + return snd_seq_oss_timer_tempo(timer, value); + case SNDCTL_TMR_TIMEBASE: + debug_printk(("timer timebase\n")); + if (get_user(value, arg)) + return -EFAULT; + if (value < MIN_OSS_TIMEBASE) + value = MIN_OSS_TIMEBASE; + else if (value > MAX_OSS_TIMEBASE) + value = MAX_OSS_TIMEBASE; + timer->oss_timebase = value; + calc_alsa_tempo(timer); + return 0; + + case SNDCTL_TMR_METRONOME: + case SNDCTL_TMR_SELECT: + case SNDCTL_TMR_SOURCE: + debug_printk(("timer XXX\n")); + /* not supported */ + return 0; + } + return 0; +} diff --git a/sound/core/seq/oss/seq_oss_timer.h b/sound/core/seq/oss/seq_oss_timer.h new file mode 100644 index 000000000000..6e4dbd8504c1 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_timer.h @@ -0,0 +1,70 @@ +/* + * OSS compatible sequencer driver + * timer handling routines + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_TIMER_H +#define __SEQ_OSS_TIMER_H + +#include "seq_oss_device.h" + +/* + * timer information definition + */ +struct seq_oss_timer_t { + seq_oss_devinfo_t *dp; + reltime_t cur_tick; + int realtime; + int running; + int tempo, ppq; /* ALSA queue */ + int oss_tempo, oss_timebase; +}; + + +seq_oss_timer_t *snd_seq_oss_timer_new(seq_oss_devinfo_t *dp); +void snd_seq_oss_timer_delete(seq_oss_timer_t *dp); + +int snd_seq_oss_timer_start(seq_oss_timer_t *timer); +int snd_seq_oss_timer_stop(seq_oss_timer_t *timer); +int snd_seq_oss_timer_continue(seq_oss_timer_t *timer); +int snd_seq_oss_timer_tempo(seq_oss_timer_t *timer, int value); +#define snd_seq_oss_timer_reset snd_seq_oss_timer_start + +int snd_seq_oss_timer_ioctl(seq_oss_timer_t *timer, unsigned int cmd, int __user *arg); + +/* + * get current processed time + */ +static inline abstime_t +snd_seq_oss_timer_cur_tick(seq_oss_timer_t *timer) +{ + return timer->cur_tick; +} + + +/* + * is realtime event? + */ +static inline int +snd_seq_oss_timer_is_realtime(seq_oss_timer_t *timer) +{ + return timer->realtime; +} + +#endif diff --git a/sound/core/seq/oss/seq_oss_writeq.c b/sound/core/seq/oss/seq_oss_writeq.c new file mode 100644 index 000000000000..87f85f7ee814 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_writeq.c @@ -0,0 +1,170 @@ +/* + * OSS compatible sequencer driver + * + * seq_oss_writeq.c - write queue and sync + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "seq_oss_writeq.h" +#include "seq_oss_event.h" +#include "seq_oss_timer.h" +#include <sound/seq_oss_legacy.h> +#include "../seq_lock.h" +#include "../seq_clientmgr.h" +#include <linux/wait.h> + + +/* + * create a write queue record + */ +seq_oss_writeq_t * +snd_seq_oss_writeq_new(seq_oss_devinfo_t *dp, int maxlen) +{ + seq_oss_writeq_t *q; + snd_seq_client_pool_t pool; + + if ((q = kcalloc(1, sizeof(*q), GFP_KERNEL)) == NULL) + return NULL; + q->dp = dp; + q->maxlen = maxlen; + spin_lock_init(&q->sync_lock); + q->sync_event_put = 0; + q->sync_time = 0; + init_waitqueue_head(&q->sync_sleep); + + memset(&pool, 0, sizeof(pool)); + pool.client = dp->cseq; + pool.output_pool = maxlen; + pool.output_room = maxlen / 2; + + snd_seq_oss_control(dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool); + + return q; +} + +/* + * delete the write queue + */ +void +snd_seq_oss_writeq_delete(seq_oss_writeq_t *q) +{ + snd_seq_oss_writeq_clear(q); /* to be sure */ + kfree(q); +} + + +/* + * reset the write queue + */ +void +snd_seq_oss_writeq_clear(seq_oss_writeq_t *q) +{ + snd_seq_remove_events_t reset; + + memset(&reset, 0, sizeof(reset)); + reset.remove_mode = SNDRV_SEQ_REMOVE_OUTPUT; /* remove all */ + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_REMOVE_EVENTS, &reset); + + /* wake up sleepers if any */ + snd_seq_oss_writeq_wakeup(q, 0); +} + +/* + * wait until the write buffer has enough room + */ +int +snd_seq_oss_writeq_sync(seq_oss_writeq_t *q) +{ + seq_oss_devinfo_t *dp = q->dp; + abstime_t time; + + time = snd_seq_oss_timer_cur_tick(dp->timer); + if (q->sync_time >= time) + return 0; /* already finished */ + + if (! q->sync_event_put) { + snd_seq_event_t ev; + evrec_t *rec; + + /* put echoback event */ + memset(&ev, 0, sizeof(ev)); + ev.flags = 0; + ev.type = SNDRV_SEQ_EVENT_ECHO; + ev.time.tick = time; + /* echo back to itself */ + snd_seq_oss_fill_addr(dp, &ev, dp->addr.client, dp->addr.port); + rec = (evrec_t*)&ev.data; + rec->t.code = SEQ_SYNCTIMER; + rec->t.time = time; + q->sync_event_put = 1; + snd_seq_kernel_client_enqueue_blocking(dp->cseq, &ev, NULL, 0, 0); + } + + wait_event_interruptible_timeout(q->sync_sleep, ! q->sync_event_put, HZ); + if (signal_pending(current)) + /* interrupted - return 0 to finish sync */ + q->sync_event_put = 0; + if (! q->sync_event_put || q->sync_time >= time) + return 0; + return 1; +} + +/* + * wake up sync - echo event was catched + */ +void +snd_seq_oss_writeq_wakeup(seq_oss_writeq_t *q, abstime_t time) +{ + unsigned long flags; + + spin_lock_irqsave(&q->sync_lock, flags); + q->sync_time = time; + q->sync_event_put = 0; + if (waitqueue_active(&q->sync_sleep)) { + wake_up(&q->sync_sleep); + } + spin_unlock_irqrestore(&q->sync_lock, flags); +} + + +/* + * return the unused pool size + */ +int +snd_seq_oss_writeq_get_free_size(seq_oss_writeq_t *q) +{ + snd_seq_client_pool_t pool; + pool.client = q->dp->cseq; + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool); + return pool.output_free; +} + + +/* + * set output threshold size from ioctl + */ +void +snd_seq_oss_writeq_set_output(seq_oss_writeq_t *q, int val) +{ + snd_seq_client_pool_t pool; + pool.client = q->dp->cseq; + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool); + pool.output_room = val; + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool); +} + diff --git a/sound/core/seq/oss/seq_oss_writeq.h b/sound/core/seq/oss/seq_oss_writeq.h new file mode 100644 index 000000000000..6a13c85e2399 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_writeq.h @@ -0,0 +1,50 @@ +/* + * OSS compatible sequencer driver + * write priority queue + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __SEQ_OSS_WRITEQ_H +#define __SEQ_OSS_WRITEQ_H + +#include "seq_oss_device.h" + + +struct seq_oss_writeq_t { + seq_oss_devinfo_t *dp; + int maxlen; + abstime_t sync_time; + int sync_event_put; + wait_queue_head_t sync_sleep; + spinlock_t sync_lock; +}; + + +/* + * seq_oss_writeq.c + */ +seq_oss_writeq_t *snd_seq_oss_writeq_new(seq_oss_devinfo_t *dp, int maxlen); +void snd_seq_oss_writeq_delete(seq_oss_writeq_t *q); +void snd_seq_oss_writeq_clear(seq_oss_writeq_t *q); +int snd_seq_oss_writeq_sync(seq_oss_writeq_t *q); +void snd_seq_oss_writeq_wakeup(seq_oss_writeq_t *q, abstime_t time); +int snd_seq_oss_writeq_get_free_size(seq_oss_writeq_t *q); +void snd_seq_oss_writeq_set_output(seq_oss_writeq_t *q, int size); + + +#endif diff --git a/sound/core/seq/seq.c b/sound/core/seq/seq.c new file mode 100644 index 000000000000..7449d2a62629 --- /dev/null +++ b/sound/core/seq/seq.c @@ -0,0 +1,147 @@ +/* + * ALSA sequencer main module + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/initval.h> + +#include <sound/seq_kernel.h> +#include "seq_clientmgr.h" +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_lock.h" +#include "seq_timer.h" +#include "seq_system.h" +#include "seq_info.h" +#include <sound/seq_device.h> + +#if defined(CONFIG_SND_SEQ_DUMMY_MODULE) +int seq_client_load[64] = {[0] = SNDRV_SEQ_CLIENT_DUMMY, [1 ... 63] = -1}; +#else +int seq_client_load[64] = {[0 ... 63] = -1}; +#endif +int seq_default_timer_class = SNDRV_TIMER_CLASS_GLOBAL; +int seq_default_timer_sclass = SNDRV_TIMER_SCLASS_NONE; +int seq_default_timer_card = -1; +int seq_default_timer_device = SNDRV_TIMER_GLOBAL_SYSTEM; +int seq_default_timer_subdevice = 0; +int seq_default_timer_resolution = 0; /* Hz */ + +MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer."); +MODULE_LICENSE("GPL"); + +module_param_array(seq_client_load, int, NULL, 0444); +MODULE_PARM_DESC(seq_client_load, "The numbers of global (system) clients to load through kmod."); +module_param(seq_default_timer_class, int, 0644); +MODULE_PARM_DESC(seq_default_timer_class, "The default timer class."); +module_param(seq_default_timer_sclass, int, 0644); +MODULE_PARM_DESC(seq_default_timer_sclass, "The default timer slave class."); +module_param(seq_default_timer_card, int, 0644); +MODULE_PARM_DESC(seq_default_timer_card, "The default timer card number."); +module_param(seq_default_timer_device, int, 0644); +MODULE_PARM_DESC(seq_default_timer_device, "The default timer device number."); +module_param(seq_default_timer_subdevice, int, 0644); +MODULE_PARM_DESC(seq_default_timer_subdevice, "The default timer subdevice number."); +module_param(seq_default_timer_resolution, int, 0644); +MODULE_PARM_DESC(seq_default_timer_resolution, "The default timer resolution in Hz."); + +/* + * INIT PART + */ + +static int __init alsa_seq_init(void) +{ + int err; + + snd_seq_autoload_lock(); + if ((err = client_init_data()) < 0) + goto error; + + /* init memory, room for selected events */ + if ((err = snd_sequencer_memory_init()) < 0) + goto error; + + /* init event queues */ + if ((err = snd_seq_queues_init()) < 0) + goto error; + + /* register sequencer device */ + if ((err = snd_sequencer_device_init()) < 0) + goto error; + + /* register proc interface */ + if ((err = snd_seq_info_init()) < 0) + goto error; + + /* register our internal client */ + if ((err = snd_seq_system_client_init()) < 0) + goto error; + + error: + snd_seq_autoload_unlock(); + return err; +} + +static void __exit alsa_seq_exit(void) +{ + /* unregister our internal client */ + snd_seq_system_client_done(); + + /* unregister proc interface */ + snd_seq_info_done(); + + /* delete timing queues */ + snd_seq_queues_delete(); + + /* unregister sequencer device */ + snd_sequencer_device_done(); + + /* release event memory */ + snd_sequencer_memory_done(); +} + +module_init(alsa_seq_init) +module_exit(alsa_seq_exit) + + /* seq_clientmgr.c */ +EXPORT_SYMBOL(snd_seq_create_kernel_client); +EXPORT_SYMBOL(snd_seq_delete_kernel_client); +EXPORT_SYMBOL(snd_seq_kernel_client_enqueue); +EXPORT_SYMBOL(snd_seq_kernel_client_enqueue_blocking); +EXPORT_SYMBOL(snd_seq_kernel_client_dispatch); +EXPORT_SYMBOL(snd_seq_kernel_client_ctl); +EXPORT_SYMBOL(snd_seq_kernel_client_write_poll); +EXPORT_SYMBOL(snd_seq_set_queue_tempo); + /* seq_memory.c */ +EXPORT_SYMBOL(snd_seq_expand_var_event); +EXPORT_SYMBOL(snd_seq_dump_var_event); + /* seq_ports.c */ +EXPORT_SYMBOL(snd_seq_event_port_attach); +EXPORT_SYMBOL(snd_seq_event_port_detach); + /* seq_lock.c */ +#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG) +/*EXPORT_SYMBOL(snd_seq_sleep_in_lock);*/ +/*EXPORT_SYMBOL(snd_seq_sleep_timeout_in_lock);*/ +EXPORT_SYMBOL(snd_use_lock_sync_helper); +#endif diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c new file mode 100644 index 000000000000..d8f76afd284b --- /dev/null +++ b/sound/core/seq/seq_clientmgr.c @@ -0,0 +1,2503 @@ +/* + * ALSA sequencer Client Manager + * Copyright (c) 1998-2001 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@suse.cz> + * Takashi Iwai <tiwai@suse.de> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <linux/kmod.h> + +#include <sound/seq_kernel.h> +#include "seq_clientmgr.h" +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_timer.h" +#include "seq_info.h" +#include "seq_system.h" +#include <sound/seq_device.h> +#ifdef CONFIG_COMPAT +#include <linux/compat.h> +#endif + +/* Client Manager + + * this module handles the connections of userland and kernel clients + * + */ + +#define SNDRV_SEQ_LFLG_INPUT 0x0001 +#define SNDRV_SEQ_LFLG_OUTPUT 0x0002 +#define SNDRV_SEQ_LFLG_OPEN (SNDRV_SEQ_LFLG_INPUT|SNDRV_SEQ_LFLG_OUTPUT) + +static DEFINE_SPINLOCK(clients_lock); +static DECLARE_MUTEX(register_mutex); + +/* + * client table + */ +static char clienttablock[SNDRV_SEQ_MAX_CLIENTS]; +static client_t *clienttab[SNDRV_SEQ_MAX_CLIENTS]; +static usage_t client_usage; + +/* + * prototypes + */ +static int bounce_error_event(client_t *client, snd_seq_event_t *event, int err, int atomic, int hop); +static int snd_seq_deliver_single_event(client_t *client, snd_seq_event_t *event, int filter, int atomic, int hop); + +/* + */ + +static inline mm_segment_t snd_enter_user(void) +{ + mm_segment_t fs = get_fs(); + set_fs(get_ds()); + return fs; +} + +static inline void snd_leave_user(mm_segment_t fs) +{ + set_fs(fs); +} + +/* + */ +static inline unsigned short snd_seq_file_flags(struct file *file) +{ + switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) { + case FMODE_WRITE: + return SNDRV_SEQ_LFLG_OUTPUT; + case FMODE_READ: + return SNDRV_SEQ_LFLG_INPUT; + default: + return SNDRV_SEQ_LFLG_OPEN; + } +} + +static inline int snd_seq_write_pool_allocated(client_t *client) +{ + return snd_seq_total_cells(client->pool) > 0; +} + +/* return pointer to client structure for specified id */ +static client_t *clientptr(int clientid) +{ + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + snd_printd("Seq: oops. Trying to get pointer to client %d\n", clientid); + return NULL; + } + return clienttab[clientid]; +} + +extern int seq_client_load[]; + +client_t *snd_seq_client_use_ptr(int clientid) +{ + unsigned long flags; + client_t *client; + + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + snd_printd("Seq: oops. Trying to get pointer to client %d\n", clientid); + return NULL; + } + spin_lock_irqsave(&clients_lock, flags); + client = clientptr(clientid); + if (client) + goto __lock; + if (clienttablock[clientid]) { + spin_unlock_irqrestore(&clients_lock, flags); + return NULL; + } + spin_unlock_irqrestore(&clients_lock, flags); +#ifdef CONFIG_KMOD + if (!in_interrupt() && current->fs->root) { + static char client_requested[64]; + static char card_requested[SNDRV_CARDS]; + if (clientid < 64) { + int idx; + + if (! client_requested[clientid] && current->fs->root) { + client_requested[clientid] = 1; + for (idx = 0; idx < 64; idx++) { + if (seq_client_load[idx] < 0) + break; + if (seq_client_load[idx] == clientid) { + request_module("snd-seq-client-%i", clientid); + break; + } + } + } + } else if (clientid >= 64 && clientid < 128) { + int card = (clientid - 64) / 8; + if (card < snd_ecards_limit) { + if (! card_requested[card]) { + card_requested[card] = 1; + snd_request_card(card); + } + snd_seq_device_load_drivers(); + } + } + spin_lock_irqsave(&clients_lock, flags); + client = clientptr(clientid); + if (client) + goto __lock; + spin_unlock_irqrestore(&clients_lock, flags); + } +#endif + return NULL; + + __lock: + snd_use_lock_use(&client->use_lock); + spin_unlock_irqrestore(&clients_lock, flags); + return client; +} + +static void usage_alloc(usage_t * res, int num) +{ + res->cur += num; + if (res->cur > res->peak) + res->peak = res->cur; +} + +static void usage_free(usage_t * res, int num) +{ + res->cur -= num; +} + +/* initialise data structures */ +int __init client_init_data(void) +{ + /* zap out the client table */ + memset(&clienttablock, 0, sizeof(clienttablock)); + memset(&clienttab, 0, sizeof(clienttab)); + return 0; +} + + +static client_t *seq_create_client1(int client_index, int poolsize) +{ + unsigned long flags; + int c; + client_t *client; + + /* init client data */ + client = kcalloc(1, sizeof(*client), GFP_KERNEL); + if (client == NULL) + return NULL; + client->pool = snd_seq_pool_new(poolsize); + if (client->pool == NULL) { + kfree(client); + return NULL; + } + client->type = NO_CLIENT; + snd_use_lock_init(&client->use_lock); + rwlock_init(&client->ports_lock); + init_MUTEX(&client->ports_mutex); + INIT_LIST_HEAD(&client->ports_list_head); + + /* find free slot in the client table */ + spin_lock_irqsave(&clients_lock, flags); + if (client_index < 0) { + for (c = 128; c < SNDRV_SEQ_MAX_CLIENTS; c++) { + if (clienttab[c] || clienttablock[c]) + continue; + clienttab[client->number = c] = client; + spin_unlock_irqrestore(&clients_lock, flags); + return client; + } + } else { + if (clienttab[client_index] == NULL && !clienttablock[client_index]) { + clienttab[client->number = client_index] = client; + spin_unlock_irqrestore(&clients_lock, flags); + return client; + } + } + spin_unlock_irqrestore(&clients_lock, flags); + snd_seq_pool_delete(&client->pool); + kfree(client); + return NULL; /* no free slot found or busy, return failure code */ +} + + +static int seq_free_client1(client_t *client) +{ + unsigned long flags; + + snd_assert(client != NULL, return -EINVAL); + snd_seq_delete_all_ports(client); + snd_seq_queue_client_leave(client->number); + spin_lock_irqsave(&clients_lock, flags); + clienttablock[client->number] = 1; + clienttab[client->number] = NULL; + spin_unlock_irqrestore(&clients_lock, flags); + snd_use_lock_sync(&client->use_lock); + snd_seq_queue_client_termination(client->number); + if (client->pool) + snd_seq_pool_delete(&client->pool); + spin_lock_irqsave(&clients_lock, flags); + clienttablock[client->number] = 0; + spin_unlock_irqrestore(&clients_lock, flags); + return 0; +} + + +static void seq_free_client(client_t * client) +{ + down(®ister_mutex); + switch (client->type) { + case NO_CLIENT: + snd_printk(KERN_WARNING "Seq: Trying to free unused client %d\n", client->number); + break; + case USER_CLIENT: + case KERNEL_CLIENT: + seq_free_client1(client); + usage_free(&client_usage, 1); + break; + + default: + snd_printk(KERN_ERR "Seq: Trying to free client %d with undefined type = %d\n", client->number, client->type); + } + up(®ister_mutex); + + snd_seq_system_client_ev_client_exit(client->number); +} + + + +/* -------------------------------------------------------- */ + +/* create a user client */ +static int snd_seq_open(struct inode *inode, struct file *file) +{ + int c, mode; /* client id */ + client_t *client; + user_client_t *user; + + if (down_interruptible(®ister_mutex)) + return -ERESTARTSYS; + client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS); + if (client == NULL) { + up(®ister_mutex); + return -ENOMEM; /* failure code */ + } + + mode = snd_seq_file_flags(file); + if (mode & SNDRV_SEQ_LFLG_INPUT) + client->accept_input = 1; + if (mode & SNDRV_SEQ_LFLG_OUTPUT) + client->accept_output = 1; + + user = &client->data.user; + user->fifo = NULL; + user->fifo_pool_size = 0; + + if (mode & SNDRV_SEQ_LFLG_INPUT) { + user->fifo_pool_size = SNDRV_SEQ_DEFAULT_CLIENT_EVENTS; + user->fifo = snd_seq_fifo_new(user->fifo_pool_size); + if (user->fifo == NULL) { + seq_free_client1(client); + kfree(client); + up(®ister_mutex); + return -ENOMEM; + } + } + + usage_alloc(&client_usage, 1); + client->type = USER_CLIENT; + up(®ister_mutex); + + c = client->number; + file->private_data = client; + + /* fill client data */ + user->file = file; + sprintf(client->name, "Client-%d", c); + + /* make others aware this new client */ + snd_seq_system_client_ev_client_start(c); + + return 0; +} + +/* delete a user client */ +static int snd_seq_release(struct inode *inode, struct file *file) +{ + client_t *client = (client_t *) file->private_data; + + if (client) { + seq_free_client(client); + if (client->data.user.fifo) + snd_seq_fifo_delete(&client->data.user.fifo); + kfree(client); + } + + return 0; +} + + +/* handle client read() */ +/* possible error values: + * -ENXIO invalid client or file open mode + * -ENOSPC FIFO overflow (the flag is cleared after this error report) + * -EINVAL no enough user-space buffer to write the whole event + * -EFAULT seg. fault during copy to user space + */ +static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + client_t *client = (client_t *) file->private_data; + fifo_t *fifo; + int err; + long result = 0; + snd_seq_event_cell_t *cell; + + if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT)) + return -ENXIO; + + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + /* check client structures are in place */ + snd_assert(client != NULL, return -ENXIO); + + if (!client->accept_input || (fifo = client->data.user.fifo) == NULL) + return -ENXIO; + + if (atomic_read(&fifo->overflow) > 0) { + /* buffer overflow is detected */ + snd_seq_fifo_clear(fifo); + /* return error code */ + return -ENOSPC; + } + + cell = NULL; + err = 0; + snd_seq_fifo_lock(fifo); + + /* while data available in queue */ + while (count >= sizeof(snd_seq_event_t)) { + int nonblock; + + nonblock = (file->f_flags & O_NONBLOCK) || result > 0; + if ((err = snd_seq_fifo_cell_out(fifo, &cell, nonblock)) < 0) { + break; + } + if (snd_seq_ev_is_variable(&cell->event)) { + snd_seq_event_t tmpev; + tmpev = cell->event; + tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; + if (copy_to_user(buf, &tmpev, sizeof(snd_seq_event_t))) { + err = -EFAULT; + break; + } + count -= sizeof(snd_seq_event_t); + buf += sizeof(snd_seq_event_t); + err = snd_seq_expand_var_event(&cell->event, count, (char *)buf, 0, sizeof(snd_seq_event_t)); + if (err < 0) + break; + result += err; + count -= err; + buf += err; + } else { + if (copy_to_user(buf, &cell->event, sizeof(snd_seq_event_t))) { + err = -EFAULT; + break; + } + count -= sizeof(snd_seq_event_t); + buf += sizeof(snd_seq_event_t); + } + snd_seq_cell_free(cell); + cell = NULL; /* to be sure */ + result += sizeof(snd_seq_event_t); + } + + if (err < 0) { + if (cell) + snd_seq_fifo_cell_putback(fifo, cell); + if (err == -EAGAIN && result > 0) + err = 0; + } + snd_seq_fifo_unlock(fifo); + + return (err < 0) ? err : result; +} + + +/* + * check access permission to the port + */ +static int check_port_perm(client_port_t *port, unsigned int flags) +{ + if ((port->capability & flags) != flags) + return 0; + return flags; +} + +/* + * check if the destination client is available, and return the pointer + * if filter is non-zero, client filter bitmap is tested. + */ +static client_t *get_event_dest_client(snd_seq_event_t *event, int filter) +{ + client_t *dest; + + dest = snd_seq_client_use_ptr(event->dest.client); + if (dest == NULL) + return NULL; + if (! dest->accept_input) + goto __not_avail; + if ((dest->filter & SNDRV_SEQ_FILTER_USE_EVENT) && + ! test_bit(event->type, dest->event_filter)) + goto __not_avail; + if (filter && !(dest->filter & filter)) + goto __not_avail; + + return dest; /* ok - accessible */ +__not_avail: + snd_seq_client_unlock(dest); + return NULL; +} + + +/* + * Return the error event. + * + * If the receiver client is a user client, the original event is + * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If + * the original event is also variable length, the external data is + * copied after the event record. + * If the receiver client is a kernel client, the original event is + * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra + * kmalloc. + */ +static int bounce_error_event(client_t *client, snd_seq_event_t *event, + int err, int atomic, int hop) +{ + snd_seq_event_t bounce_ev; + int result; + + if (client == NULL || + ! (client->filter & SNDRV_SEQ_FILTER_BOUNCE) || + ! client->accept_input) + return 0; /* ignored */ + + /* set up quoted error */ + memset(&bounce_ev, 0, sizeof(bounce_ev)); + bounce_ev.type = SNDRV_SEQ_EVENT_KERNEL_ERROR; + bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + bounce_ev.queue = SNDRV_SEQ_QUEUE_DIRECT; + bounce_ev.source.client = SNDRV_SEQ_CLIENT_SYSTEM; + bounce_ev.source.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + bounce_ev.dest.client = client->number; + bounce_ev.dest.port = event->source.port; + bounce_ev.data.quote.origin = event->dest; + bounce_ev.data.quote.event = event; + bounce_ev.data.quote.value = -err; /* use positive value */ + result = snd_seq_deliver_single_event(NULL, &bounce_ev, 0, atomic, hop + 1); + if (result < 0) { + client->event_lost++; + return result; + } + + return result; +} + + +/* + * rewrite the time-stamp of the event record with the curren time + * of the given queue. + * return non-zero if updated. + */ +static int update_timestamp_of_queue(snd_seq_event_t *event, int queue, int real_time) +{ + queue_t *q; + + q = queueptr(queue); + if (! q) + return 0; + event->queue = queue; + event->flags &= ~SNDRV_SEQ_TIME_STAMP_MASK; + if (real_time) { + event->time.time = snd_seq_timer_get_cur_time(q->timer); + event->flags |= SNDRV_SEQ_TIME_STAMP_REAL; + } else { + event->time.tick = snd_seq_timer_get_cur_tick(q->timer); + event->flags |= SNDRV_SEQ_TIME_STAMP_TICK; + } + queuefree(q); + return 1; +} + + +/* + * deliver an event to the specified destination. + * if filter is non-zero, client filter bitmap is tested. + * + * RETURN VALUE: 0 : if succeeded + * <0 : error + */ +static int snd_seq_deliver_single_event(client_t *client, + snd_seq_event_t *event, + int filter, int atomic, int hop) +{ + client_t *dest = NULL; + client_port_t *dest_port = NULL; + int result = -ENOENT; + int direct; + + direct = snd_seq_ev_is_direct(event); + + dest = get_event_dest_client(event, filter); + if (dest == NULL) + goto __skip; + dest_port = snd_seq_port_use_ptr(dest, event->dest.port); + if (dest_port == NULL) + goto __skip; + + /* check permission */ + if (! check_port_perm(dest_port, SNDRV_SEQ_PORT_CAP_WRITE)) { + result = -EPERM; + goto __skip; + } + + if (dest_port->timestamping) + update_timestamp_of_queue(event, dest_port->time_queue, + dest_port->time_real); + + switch (dest->type) { + case USER_CLIENT: + if (dest->data.user.fifo) + result = snd_seq_fifo_event_in(dest->data.user.fifo, event); + break; + + case KERNEL_CLIENT: + if (dest_port->event_input == NULL) + break; + result = dest_port->event_input(event, direct, dest_port->private_data, atomic, hop); + break; + default: + break; + } + + __skip: + if (dest_port) + snd_seq_port_unlock(dest_port); + if (dest) + snd_seq_client_unlock(dest); + + if (result < 0 && !direct) { + result = bounce_error_event(client, event, result, atomic, hop); + } + return result; +} + + +/* + * send the event to all subscribers: + */ +static int deliver_to_subscribers(client_t *client, + snd_seq_event_t *event, + int atomic, int hop) +{ + subscribers_t *subs; + int err = 0, num_ev = 0; + snd_seq_event_t event_saved; + client_port_t *src_port; + struct list_head *p; + port_subs_info_t *grp; + + src_port = snd_seq_port_use_ptr(client, event->source.port); + if (src_port == NULL) + return -EINVAL; /* invalid source port */ + /* save original event record */ + event_saved = *event; + grp = &src_port->c_src; + + /* lock list */ + if (atomic) + read_lock(&grp->list_lock); + else + down_read(&grp->list_mutex); + list_for_each(p, &grp->list_head) { + subs = list_entry(p, subscribers_t, src_list); + event->dest = subs->info.dest; + if (subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP) + /* convert time according to flag with subscription */ + update_timestamp_of_queue(event, subs->info.queue, + subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL); + err = snd_seq_deliver_single_event(client, event, + 0, atomic, hop); + if (err < 0) + break; + num_ev++; + /* restore original event record */ + *event = event_saved; + } + if (atomic) + read_unlock(&grp->list_lock); + else + up_read(&grp->list_mutex); + *event = event_saved; /* restore */ + snd_seq_port_unlock(src_port); + return (err < 0) ? err : num_ev; +} + + +#ifdef SUPPORT_BROADCAST +/* + * broadcast to all ports: + */ +static int port_broadcast_event(client_t *client, + snd_seq_event_t *event, + int atomic, int hop) +{ + int num_ev = 0, err = 0; + client_t *dest_client; + struct list_head *p; + + dest_client = get_event_dest_client(event, SNDRV_SEQ_FILTER_BROADCAST); + if (dest_client == NULL) + return 0; /* no matching destination */ + + read_lock(&dest_client->ports_lock); + list_for_each(p, &dest_client->ports_list_head) { + client_port_t *port = list_entry(p, client_port_t, list); + event->dest.port = port->addr.port; + /* pass NULL as source client to avoid error bounce */ + err = snd_seq_deliver_single_event(NULL, event, + SNDRV_SEQ_FILTER_BROADCAST, + atomic, hop); + if (err < 0) + break; + num_ev++; + } + read_unlock(&dest_client->ports_lock); + snd_seq_client_unlock(dest_client); + event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */ + return (err < 0) ? err : num_ev; +} + +/* + * send the event to all clients: + * if destination port is also ADDRESS_BROADCAST, deliver to all ports. + */ +static int broadcast_event(client_t *client, + snd_seq_event_t *event, int atomic, int hop) +{ + int err = 0, num_ev = 0; + int dest; + snd_seq_addr_t addr; + + addr = event->dest; /* save */ + + for (dest = 0; dest < SNDRV_SEQ_MAX_CLIENTS; dest++) { + /* don't send to itself */ + if (dest == client->number) + continue; + event->dest.client = dest; + event->dest.port = addr.port; + if (addr.port == SNDRV_SEQ_ADDRESS_BROADCAST) + err = port_broadcast_event(client, event, atomic, hop); + else + /* pass NULL as source client to avoid error bounce */ + err = snd_seq_deliver_single_event(NULL, event, + SNDRV_SEQ_FILTER_BROADCAST, + atomic, hop); + if (err < 0) + break; + num_ev += err; + } + event->dest = addr; /* restore */ + return (err < 0) ? err : num_ev; +} + + +/* multicast - not supported yet */ +static int multicast_event(client_t *client, snd_seq_event_t *event, + int atomic, int hop) +{ + snd_printd("seq: multicast not supported yet.\n"); + return 0; /* ignored */ +} +#endif /* SUPPORT_BROADCAST */ + + +/* deliver an event to the destination port(s). + * if the event is to subscribers or broadcast, the event is dispatched + * to multiple targets. + * + * RETURN VALUE: n > 0 : the number of delivered events. + * n == 0 : the event was not passed to any client. + * n < 0 : error - event was not processed. + */ +static int snd_seq_deliver_event(client_t *client, snd_seq_event_t *event, + int atomic, int hop) +{ + int result; + + hop++; + if (hop >= SNDRV_SEQ_MAX_HOPS) { + snd_printd("too long delivery path (%d:%d->%d:%d)\n", + event->source.client, event->source.port, + event->dest.client, event->dest.port); + return -EMLINK; + } + + if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS || + event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) + result = deliver_to_subscribers(client, event, atomic, hop); +#ifdef SUPPORT_BROADCAST + else if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST || + event->dest.client == SNDRV_SEQ_ADDRESS_BROADCAST) + result = broadcast_event(client, event, atomic, hop); + else if (event->dest.client >= SNDRV_SEQ_MAX_CLIENTS) + result = multicast_event(client, event, atomic, hop); + else if (event->dest.port == SNDRV_SEQ_ADDRESS_BROADCAST) + result = port_broadcast_event(client, event, atomic, hop); +#endif + else + result = snd_seq_deliver_single_event(client, event, 0, atomic, hop); + + return result; +} + +/* + * dispatch an event cell: + * This function is called only from queue check routines in timer + * interrupts or after enqueued. + * The event cell shall be released or re-queued in this function. + * + * RETURN VALUE: n > 0 : the number of delivered events. + * n == 0 : the event was not passed to any client. + * n < 0 : error - event was not processed. + */ +int snd_seq_dispatch_event(snd_seq_event_cell_t *cell, int atomic, int hop) +{ + client_t *client; + int result; + + snd_assert(cell != NULL, return -EINVAL); + + client = snd_seq_client_use_ptr(cell->event.source.client); + if (client == NULL) { + snd_seq_cell_free(cell); /* release this cell */ + return -EINVAL; + } + + if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) { + /* NOTE event: + * the event cell is re-used as a NOTE-OFF event and + * enqueued again. + */ + snd_seq_event_t tmpev, *ev; + + /* reserve this event to enqueue note-off later */ + tmpev = cell->event; + tmpev.type = SNDRV_SEQ_EVENT_NOTEON; + result = snd_seq_deliver_event(client, &tmpev, atomic, hop); + + /* + * This was originally a note event. We now re-use the + * cell for the note-off event. + */ + + ev = &cell->event; + ev->type = SNDRV_SEQ_EVENT_NOTEOFF; + ev->flags |= SNDRV_SEQ_PRIORITY_HIGH; + + /* add the duration time */ + switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + ev->time.tick += ev->data.note.duration; + break; + case SNDRV_SEQ_TIME_STAMP_REAL: + /* unit for duration is ms */ + ev->time.time.tv_nsec += 1000000 * (ev->data.note.duration % 1000); + ev->time.time.tv_sec += ev->data.note.duration / 1000 + + ev->time.time.tv_nsec / 1000000000; + ev->time.time.tv_nsec %= 1000000000; + break; + } + ev->data.note.velocity = ev->data.note.off_velocity; + + /* Now queue this cell as the note off event */ + if (snd_seq_enqueue_event(cell, atomic, hop) < 0) + snd_seq_cell_free(cell); /* release this cell */ + + } else { + /* Normal events: + * event cell is freed after processing the event + */ + + result = snd_seq_deliver_event(client, &cell->event, atomic, hop); + snd_seq_cell_free(cell); + } + + snd_seq_client_unlock(client); + return result; +} + + +/* Allocate a cell from client pool and enqueue it to queue: + * if pool is empty and blocking is TRUE, sleep until a new cell is + * available. + */ +static int snd_seq_client_enqueue_event(client_t *client, + snd_seq_event_t *event, + struct file *file, int blocking, + int atomic, int hop) +{ + snd_seq_event_cell_t *cell; + int err; + + /* special queue values - force direct passing */ + if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { + event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + event->queue = SNDRV_SEQ_QUEUE_DIRECT; + } else +#ifdef SUPPORT_BROADCAST + if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST) { + event->dest.client = SNDRV_SEQ_ADDRESS_BROADCAST; + event->queue = SNDRV_SEQ_QUEUE_DIRECT; + } +#endif + if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { + /* check presence of source port */ + client_port_t *src_port = snd_seq_port_use_ptr(client, event->source.port); + if (src_port == NULL) + return -EINVAL; + snd_seq_port_unlock(src_port); + } + + /* direct event processing without enqueued */ + if (snd_seq_ev_is_direct(event)) { + if (event->type == SNDRV_SEQ_EVENT_NOTE) + return -EINVAL; /* this event must be enqueued! */ + return snd_seq_deliver_event(client, event, atomic, hop); + } + + /* Not direct, normal queuing */ + if (snd_seq_queue_is_used(event->queue, client->number) <= 0) + return -EINVAL; /* invalid queue */ + if (! snd_seq_write_pool_allocated(client)) + return -ENXIO; /* queue is not allocated */ + + /* allocate an event cell */ + err = snd_seq_event_dup(client->pool, event, &cell, !blocking || atomic, file); + if (err < 0) + return err; + + /* we got a cell. enqueue it. */ + if ((err = snd_seq_enqueue_event(cell, atomic, hop)) < 0) { + snd_seq_cell_free(cell); + return err; + } + + return 0; +} + + +/* + * check validity of event type and data length. + * return non-zero if invalid. + */ +static int check_event_type_and_length(snd_seq_event_t *ev) +{ + switch (snd_seq_ev_length_type(ev)) { + case SNDRV_SEQ_EVENT_LENGTH_FIXED: + if (snd_seq_ev_is_variable_type(ev)) + return -EINVAL; + break; + case SNDRV_SEQ_EVENT_LENGTH_VARIABLE: + if (! snd_seq_ev_is_variable_type(ev) || + (ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK) >= SNDRV_SEQ_MAX_EVENT_LEN) + return -EINVAL; + break; + case SNDRV_SEQ_EVENT_LENGTH_VARUSR: + if (! snd_seq_ev_is_instr_type(ev) || + ! snd_seq_ev_is_direct(ev)) + return -EINVAL; + break; + } + return 0; +} + + +/* handle write() */ +/* possible error values: + * -ENXIO invalid client or file open mode + * -ENOMEM malloc failed + * -EFAULT seg. fault during copy from user space + * -EINVAL invalid event + * -EAGAIN no space in output pool + * -EINTR interrupts while sleep + * -EMLINK too many hops + * others depends on return value from driver callback + */ +static ssize_t snd_seq_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ + client_t *client = (client_t *) file->private_data; + int written = 0, len; + int err = -EINVAL; + snd_seq_event_t event; + + if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT)) + return -ENXIO; + + /* check client structures are in place */ + snd_assert(client != NULL, return -ENXIO); + + if (!client->accept_output || client->pool == NULL) + return -ENXIO; + + /* allocate the pool now if the pool is not allocated yet */ + if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) { + if (snd_seq_pool_init(client->pool) < 0) + return -ENOMEM; + } + + /* only process whole events */ + while (count >= sizeof(snd_seq_event_t)) { + /* Read in the event header from the user */ + len = sizeof(event); + if (copy_from_user(&event, buf, len)) { + err = -EFAULT; + break; + } + event.source.client = client->number; /* fill in client number */ + /* Check for extension data length */ + if (check_event_type_and_length(&event)) { + err = -EINVAL; + break; + } + + /* check for special events */ + if (event.type == SNDRV_SEQ_EVENT_NONE) + goto __skip_event; + else if (snd_seq_ev_is_reserved(&event)) { + err = -EINVAL; + break; + } + + if (snd_seq_ev_is_variable(&event)) { + int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK; + if ((size_t)(extlen + len) > count) { + /* back out, will get an error this time or next */ + err = -EINVAL; + break; + } + /* set user space pointer */ + event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR; + event.data.ext.ptr = (char*)buf + sizeof(snd_seq_event_t); + len += extlen; /* increment data length */ + } else { +#ifdef CONFIG_COMPAT + if (client->convert32 && snd_seq_ev_is_varusr(&event)) { + void *ptr = compat_ptr(event.data.raw32.d[1]); + event.data.ext.ptr = ptr; + } +#endif + } + + /* ok, enqueue it */ + err = snd_seq_client_enqueue_event(client, &event, file, + !(file->f_flags & O_NONBLOCK), + 0, 0); + if (err < 0) + break; + + __skip_event: + /* Update pointers and counts */ + count -= len; + buf += len; + written += len; + } + + return written ? written : err; +} + + +/* + * handle polling + */ +static unsigned int snd_seq_poll(struct file *file, poll_table * wait) +{ + client_t *client = (client_t *) file->private_data; + unsigned int mask = 0; + + /* check client structures are in place */ + snd_assert(client != NULL, return -ENXIO); + + if ((snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT) && + client->data.user.fifo) { + + /* check if data is available in the outqueue */ + if (snd_seq_fifo_poll_wait(client->data.user.fifo, file, wait)) + mask |= POLLIN | POLLRDNORM; + } + + if (snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT) { + + /* check if data is available in the pool */ + if (!snd_seq_write_pool_allocated(client) || + snd_seq_pool_poll_wait(client->pool, file, wait)) + mask |= POLLOUT | POLLWRNORM; + } + + return mask; +} + + +/*-----------------------------------------------------*/ + + +/* SYSTEM_INFO ioctl() */ +static int snd_seq_ioctl_system_info(client_t *client, void __user *arg) +{ + snd_seq_system_info_t info; + + memset(&info, 0, sizeof(info)); + /* fill the info fields */ + info.queues = SNDRV_SEQ_MAX_QUEUES; + info.clients = SNDRV_SEQ_MAX_CLIENTS; + info.ports = 256; /* fixed limit */ + info.channels = 256; /* fixed limit */ + info.cur_clients = client_usage.cur; + info.cur_queues = snd_seq_queue_get_cur_queues(); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + + +/* RUNNING_MODE ioctl() */ +static int snd_seq_ioctl_running_mode(client_t *client, void __user *arg) +{ + struct sndrv_seq_running_info info; + client_t *cptr; + int err = 0; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + /* requested client number */ + cptr = snd_seq_client_use_ptr(info.client); + if (cptr == NULL) + return -ENOENT; /* don't change !!! */ + +#ifdef SNDRV_BIG_ENDIAN + if (! info.big_endian) { + err = -EINVAL; + goto __err; + } +#else + if (info.big_endian) { + err = -EINVAL; + goto __err; + } + +#endif + if (info.cpu_mode > sizeof(long)) { + err = -EINVAL; + goto __err; + } + cptr->convert32 = (info.cpu_mode < sizeof(long)); + __err: + snd_seq_client_unlock(cptr); + return err; +} + +/* CLIENT_INFO ioctl() */ +static void get_client_info(client_t *cptr, snd_seq_client_info_t *info) +{ + info->client = cptr->number; + + /* fill the info fields */ + info->type = cptr->type; + strcpy(info->name, cptr->name); + info->filter = cptr->filter; + info->event_lost = cptr->event_lost; + memcpy(info->event_filter, cptr->event_filter, 32); + info->num_ports = cptr->num_ports; + memset(info->reserved, 0, sizeof(info->reserved)); +} + +static int snd_seq_ioctl_get_client_info(client_t * client, void __user *arg) +{ + client_t *cptr; + snd_seq_client_info_t client_info; + + if (copy_from_user(&client_info, arg, sizeof(client_info))) + return -EFAULT; + + /* requested client number */ + cptr = snd_seq_client_use_ptr(client_info.client); + if (cptr == NULL) + return -ENOENT; /* don't change !!! */ + + get_client_info(cptr, &client_info); + snd_seq_client_unlock(cptr); + + if (copy_to_user(arg, &client_info, sizeof(client_info))) + return -EFAULT; + return 0; +} + + +/* CLIENT_INFO ioctl() */ +static int snd_seq_ioctl_set_client_info(client_t * client, void __user *arg) +{ + snd_seq_client_info_t client_info; + + if (copy_from_user(&client_info, arg, sizeof(client_info))) + return -EFAULT; + + /* it is not allowed to set the info fields for an another client */ + if (client->number != client_info.client) + return -EPERM; + /* also client type must be set now */ + if (client->type != client_info.type) + return -EINVAL; + + /* fill the info fields */ + if (client_info.name[0]) + strlcpy(client->name, client_info.name, sizeof(client->name)); + + client->filter = client_info.filter; + client->event_lost = client_info.event_lost; + memcpy(client->event_filter, client_info.event_filter, 32); + + return 0; +} + + +/* + * CREATE PORT ioctl() + */ +static int snd_seq_ioctl_create_port(client_t * client, void __user *arg) +{ + client_port_t *port; + snd_seq_port_info_t info; + snd_seq_port_callback_t *callback; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + /* it is not allowed to create the port for an another client */ + if (info.addr.client != client->number) + return -EPERM; + + port = snd_seq_create_port(client, (info.flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) ? info.addr.port : -1); + if (port == NULL) + return -ENOMEM; + + if (client->type == USER_CLIENT && info.kernel) { + snd_seq_delete_port(client, port->addr.port); + return -EINVAL; + } + if (client->type == KERNEL_CLIENT) { + if ((callback = info.kernel) != NULL) { + if (callback->owner) + port->owner = callback->owner; + port->private_data = callback->private_data; + port->private_free = callback->private_free; + port->callback_all = callback->callback_all; + port->event_input = callback->event_input; + port->c_src.open = callback->subscribe; + port->c_src.close = callback->unsubscribe; + port->c_dest.open = callback->use; + port->c_dest.close = callback->unuse; + } + } + + info.addr = port->addr; + + snd_seq_set_port_info(port, &info); + snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +/* + * DELETE PORT ioctl() + */ +static int snd_seq_ioctl_delete_port(client_t * client, void __user *arg) +{ + snd_seq_port_info_t info; + int err; + + /* set passed parameters */ + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + /* it is not allowed to remove the port for an another client */ + if (info.addr.client != client->number) + return -EPERM; + + err = snd_seq_delete_port(client, info.addr.port); + if (err >= 0) + snd_seq_system_client_ev_port_exit(client->number, info.addr.port); + return err; +} + + +/* + * GET_PORT_INFO ioctl() (on any client) + */ +static int snd_seq_ioctl_get_port_info(client_t *client, void __user *arg) +{ + client_t *cptr; + client_port_t *port; + snd_seq_port_info_t info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + cptr = snd_seq_client_use_ptr(info.addr.client); + if (cptr == NULL) + return -ENXIO; + + port = snd_seq_port_use_ptr(cptr, info.addr.port); + if (port == NULL) { + snd_seq_client_unlock(cptr); + return -ENOENT; /* don't change */ + } + + /* get port info */ + snd_seq_get_port_info(port, &info); + snd_seq_port_unlock(port); + snd_seq_client_unlock(cptr); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + + +/* + * SET_PORT_INFO ioctl() (only ports on this/own client) + */ +static int snd_seq_ioctl_set_port_info(client_t * client, void __user *arg) +{ + client_port_t *port; + snd_seq_port_info_t info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + if (info.addr.client != client->number) /* only set our own ports ! */ + return -EPERM; + port = snd_seq_port_use_ptr(client, info.addr.port); + if (port) { + snd_seq_set_port_info(port, &info); + snd_seq_port_unlock(port); + } + return 0; +} + + +/* + * port subscription (connection) + */ +#define PERM_RD (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ) +#define PERM_WR (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE) + +static int check_subscription_permission(client_t *client, client_port_t *sport, + client_port_t *dport, + snd_seq_port_subscribe_t *subs) +{ + if (client->number != subs->sender.client && + client->number != subs->dest.client) { + /* connection by third client - check export permission */ + if (check_port_perm(sport, SNDRV_SEQ_PORT_CAP_NO_EXPORT)) + return -EPERM; + if (check_port_perm(dport, SNDRV_SEQ_PORT_CAP_NO_EXPORT)) + return -EPERM; + } + + /* check read permission */ + /* if sender or receiver is the subscribing client itself, + * no permission check is necessary + */ + if (client->number != subs->sender.client) { + if (! check_port_perm(sport, PERM_RD)) + return -EPERM; + } + /* check write permission */ + if (client->number != subs->dest.client) { + if (! check_port_perm(dport, PERM_WR)) + return -EPERM; + } + return 0; +} + +/* + * send an subscription notify event to user client: + * client must be user client. + */ +int snd_seq_client_notify_subscription(int client, int port, + snd_seq_port_subscribe_t *info, int evtype) +{ + snd_seq_event_t event; + + memset(&event, 0, sizeof(event)); + event.type = evtype; + event.data.connect.dest = info->dest; + event.data.connect.sender = info->sender; + + return snd_seq_system_notify(client, port, &event); /* non-atomic */ +} + + +/* + * add to port's subscription list IOCTL interface + */ +static int snd_seq_ioctl_subscribe_port(client_t * client, void __user *arg) +{ + int result = -EINVAL; + client_t *receiver = NULL, *sender = NULL; + client_port_t *sport = NULL, *dport = NULL; + snd_seq_port_subscribe_t subs; + + if (copy_from_user(&subs, arg, sizeof(subs))) + return -EFAULT; + + if ((receiver = snd_seq_client_use_ptr(subs.dest.client)) == NULL) + goto __end; + if ((sender = snd_seq_client_use_ptr(subs.sender.client)) == NULL) + goto __end; + if ((sport = snd_seq_port_use_ptr(sender, subs.sender.port)) == NULL) + goto __end; + if ((dport = snd_seq_port_use_ptr(receiver, subs.dest.port)) == NULL) + goto __end; + + result = check_subscription_permission(client, sport, dport, &subs); + if (result < 0) + goto __end; + + /* connect them */ + result = snd_seq_port_connect(client, sender, sport, receiver, dport, &subs); + if (! result) /* broadcast announce */ + snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0, + &subs, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED); + __end: + if (sport) + snd_seq_port_unlock(sport); + if (dport) + snd_seq_port_unlock(dport); + if (sender) + snd_seq_client_unlock(sender); + if (receiver) + snd_seq_client_unlock(receiver); + return result; +} + + +/* + * remove from port's subscription list + */ +static int snd_seq_ioctl_unsubscribe_port(client_t * client, void __user *arg) +{ + int result = -ENXIO; + client_t *receiver = NULL, *sender = NULL; + client_port_t *sport = NULL, *dport = NULL; + snd_seq_port_subscribe_t subs; + + if (copy_from_user(&subs, arg, sizeof(subs))) + return -EFAULT; + + if ((receiver = snd_seq_client_use_ptr(subs.dest.client)) == NULL) + goto __end; + if ((sender = snd_seq_client_use_ptr(subs.sender.client)) == NULL) + goto __end; + if ((sport = snd_seq_port_use_ptr(sender, subs.sender.port)) == NULL) + goto __end; + if ((dport = snd_seq_port_use_ptr(receiver, subs.dest.port)) == NULL) + goto __end; + + result = check_subscription_permission(client, sport, dport, &subs); + if (result < 0) + goto __end; + + result = snd_seq_port_disconnect(client, sender, sport, receiver, dport, &subs); + if (! result) /* broadcast announce */ + snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0, + &subs, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED); + __end: + if (sport) + snd_seq_port_unlock(sport); + if (dport) + snd_seq_port_unlock(dport); + if (sender) + snd_seq_client_unlock(sender); + if (receiver) + snd_seq_client_unlock(receiver); + return result; +} + + +/* CREATE_QUEUE ioctl() */ +static int snd_seq_ioctl_create_queue(client_t *client, void __user *arg) +{ + snd_seq_queue_info_t info; + int result; + queue_t *q; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + result = snd_seq_queue_alloc(client->number, info.locked, info.flags); + if (result < 0) + return result; + + q = queueptr(result); + if (q == NULL) + return -EINVAL; + + info.queue = q->queue; + info.locked = q->locked; + info.owner = q->owner; + + /* set queue name */ + if (! info.name[0]) + snprintf(info.name, sizeof(info.name), "Queue-%d", q->queue); + strlcpy(q->name, info.name, sizeof(q->name)); + queuefree(q); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +/* DELETE_QUEUE ioctl() */ +static int snd_seq_ioctl_delete_queue(client_t *client, void __user *arg) +{ + snd_seq_queue_info_t info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + return snd_seq_queue_delete(client->number, info.queue); +} + +/* GET_QUEUE_INFO ioctl() */ +static int snd_seq_ioctl_get_queue_info(client_t *client, void __user *arg) +{ + snd_seq_queue_info_t info; + queue_t *q; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + q = queueptr(info.queue); + if (q == NULL) + return -EINVAL; + + memset(&info, 0, sizeof(info)); + info.queue = q->queue; + info.owner = q->owner; + info.locked = q->locked; + strlcpy(info.name, q->name, sizeof(info.name)); + queuefree(q); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +/* SET_QUEUE_INFO ioctl() */ +static int snd_seq_ioctl_set_queue_info(client_t *client, void __user *arg) +{ + snd_seq_queue_info_t info; + queue_t *q; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + if (info.owner != client->number) + return -EINVAL; + + /* change owner/locked permission */ + if (snd_seq_queue_check_access(info.queue, client->number)) { + if (snd_seq_queue_set_owner(info.queue, client->number, info.locked) < 0) + return -EPERM; + if (info.locked) + snd_seq_queue_use(info.queue, client->number, 1); + } else { + return -EPERM; + } + + q = queueptr(info.queue); + if (! q) + return -EINVAL; + if (q->owner != client->number) { + queuefree(q); + return -EPERM; + } + strlcpy(q->name, info.name, sizeof(q->name)); + queuefree(q); + + return 0; +} + +/* GET_NAMED_QUEUE ioctl() */ +static int snd_seq_ioctl_get_named_queue(client_t *client, void __user *arg) +{ + snd_seq_queue_info_t info; + queue_t *q; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + q = snd_seq_queue_find_name(info.name); + if (q == NULL) + return -EINVAL; + info.queue = q->queue; + info.owner = q->owner; + info.locked = q->locked; + queuefree(q); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +/* GET_QUEUE_STATUS ioctl() */ +static int snd_seq_ioctl_get_queue_status(client_t * client, void __user *arg) +{ + snd_seq_queue_status_t status; + queue_t *queue; + seq_timer_t *tmr; + + if (copy_from_user(&status, arg, sizeof(status))) + return -EFAULT; + + queue = queueptr(status.queue); + if (queue == NULL) + return -EINVAL; + memset(&status, 0, sizeof(status)); + status.queue = queue->queue; + + tmr = queue->timer; + status.events = queue->tickq->cells + queue->timeq->cells; + + status.time = snd_seq_timer_get_cur_time(tmr); + status.tick = snd_seq_timer_get_cur_tick(tmr); + + status.running = tmr->running; + + status.flags = queue->flags; + queuefree(queue); + + if (copy_to_user(arg, &status, sizeof(status))) + return -EFAULT; + return 0; +} + + +/* GET_QUEUE_TEMPO ioctl() */ +static int snd_seq_ioctl_get_queue_tempo(client_t * client, void __user *arg) +{ + snd_seq_queue_tempo_t tempo; + queue_t *queue; + seq_timer_t *tmr; + + if (copy_from_user(&tempo, arg, sizeof(tempo))) + return -EFAULT; + + queue = queueptr(tempo.queue); + if (queue == NULL) + return -EINVAL; + memset(&tempo, 0, sizeof(tempo)); + tempo.queue = queue->queue; + + tmr = queue->timer; + + tempo.tempo = tmr->tempo; + tempo.ppq = tmr->ppq; + tempo.skew_value = tmr->skew; + tempo.skew_base = tmr->skew_base; + queuefree(queue); + + if (copy_to_user(arg, &tempo, sizeof(tempo))) + return -EFAULT; + return 0; +} + + +/* SET_QUEUE_TEMPO ioctl() */ +int snd_seq_set_queue_tempo(int client, snd_seq_queue_tempo_t *tempo) +{ + if (!snd_seq_queue_check_access(tempo->queue, client)) + return -EPERM; + return snd_seq_queue_timer_set_tempo(tempo->queue, client, tempo); +} + +static int snd_seq_ioctl_set_queue_tempo(client_t * client, void __user *arg) +{ + int result; + snd_seq_queue_tempo_t tempo; + + if (copy_from_user(&tempo, arg, sizeof(tempo))) + return -EFAULT; + + result = snd_seq_set_queue_tempo(client->number, &tempo); + return result < 0 ? result : 0; +} + + +/* GET_QUEUE_TIMER ioctl() */ +static int snd_seq_ioctl_get_queue_timer(client_t * client, void __user *arg) +{ + snd_seq_queue_timer_t timer; + queue_t *queue; + seq_timer_t *tmr; + + if (copy_from_user(&timer, arg, sizeof(timer))) + return -EFAULT; + + queue = queueptr(timer.queue); + if (queue == NULL) + return -EINVAL; + + if (down_interruptible(&queue->timer_mutex)) { + queuefree(queue); + return -ERESTARTSYS; + } + tmr = queue->timer; + memset(&timer, 0, sizeof(timer)); + timer.queue = queue->queue; + + timer.type = tmr->type; + if (tmr->type == SNDRV_SEQ_TIMER_ALSA) { + timer.u.alsa.id = tmr->alsa_id; + timer.u.alsa.resolution = tmr->preferred_resolution; + } + up(&queue->timer_mutex); + queuefree(queue); + + if (copy_to_user(arg, &timer, sizeof(timer))) + return -EFAULT; + return 0; +} + + +/* SET_QUEUE_TIMER ioctl() */ +static int snd_seq_ioctl_set_queue_timer(client_t * client, void __user *arg) +{ + int result = 0; + snd_seq_queue_timer_t timer; + + if (copy_from_user(&timer, arg, sizeof(timer))) + return -EFAULT; + + if (timer.type != SNDRV_SEQ_TIMER_ALSA) + return -EINVAL; + + if (snd_seq_queue_check_access(timer.queue, client->number)) { + queue_t *q; + seq_timer_t *tmr; + + q = queueptr(timer.queue); + if (q == NULL) + return -ENXIO; + if (down_interruptible(&q->timer_mutex)) { + queuefree(q); + return -ERESTARTSYS; + } + tmr = q->timer; + snd_seq_queue_timer_close(timer.queue); + tmr->type = timer.type; + if (tmr->type == SNDRV_SEQ_TIMER_ALSA) { + tmr->alsa_id = timer.u.alsa.id; + tmr->preferred_resolution = timer.u.alsa.resolution; + } + result = snd_seq_queue_timer_open(timer.queue); + up(&q->timer_mutex); + queuefree(q); + } else { + return -EPERM; + } + + return result; +} + + +/* GET_QUEUE_CLIENT ioctl() */ +static int snd_seq_ioctl_get_queue_client(client_t * client, void __user *arg) +{ + snd_seq_queue_client_t info; + int used; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + used = snd_seq_queue_is_used(info.queue, client->number); + if (used < 0) + return -EINVAL; + info.used = used; + info.client = client->number; + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + + +/* SET_QUEUE_CLIENT ioctl() */ +static int snd_seq_ioctl_set_queue_client(client_t * client, void __user *arg) +{ + int err; + snd_seq_queue_client_t info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + if (info.used >= 0) { + err = snd_seq_queue_use(info.queue, client->number, info.used); + if (err < 0) + return err; + } + + return snd_seq_ioctl_get_queue_client(client, arg); +} + + +/* GET_CLIENT_POOL ioctl() */ +static int snd_seq_ioctl_get_client_pool(client_t * client, void __user *arg) +{ + snd_seq_client_pool_t info; + client_t *cptr; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + cptr = snd_seq_client_use_ptr(info.client); + if (cptr == NULL) + return -ENOENT; + memset(&info, 0, sizeof(info)); + info.output_pool = cptr->pool->size; + info.output_room = cptr->pool->room; + info.output_free = info.output_pool; + if (cptr->pool) + info.output_free = snd_seq_unused_cells(cptr->pool); + if (cptr->type == USER_CLIENT) { + info.input_pool = cptr->data.user.fifo_pool_size; + info.input_free = info.input_pool; + if (cptr->data.user.fifo) + info.input_free = snd_seq_unused_cells(cptr->data.user.fifo->pool); + } else { + info.input_pool = 0; + info.input_free = 0; + } + snd_seq_client_unlock(cptr); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +/* SET_CLIENT_POOL ioctl() */ +static int snd_seq_ioctl_set_client_pool(client_t * client, void __user *arg) +{ + snd_seq_client_pool_t info; + int rc; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + if (client->number != info.client) + return -EINVAL; /* can't change other clients */ + + if (info.output_pool >= 1 && info.output_pool <= SNDRV_SEQ_MAX_EVENTS && + (! snd_seq_write_pool_allocated(client) || + info.output_pool != client->pool->size)) { + if (snd_seq_write_pool_allocated(client)) { + /* remove all existing cells */ + snd_seq_queue_client_leave_cells(client->number); + snd_seq_pool_done(client->pool); + } + client->pool->size = info.output_pool; + rc = snd_seq_pool_init(client->pool); + if (rc < 0) + return rc; + } + if (client->type == USER_CLIENT && client->data.user.fifo != NULL && + info.input_pool >= 1 && + info.input_pool <= SNDRV_SEQ_MAX_CLIENT_EVENTS && + info.input_pool != client->data.user.fifo_pool_size) { + /* change pool size */ + rc = snd_seq_fifo_resize(client->data.user.fifo, info.input_pool); + if (rc < 0) + return rc; + client->data.user.fifo_pool_size = info.input_pool; + } + if (info.output_room >= 1 && + info.output_room <= client->pool->size) { + client->pool->room = info.output_room; + } + + return snd_seq_ioctl_get_client_pool(client, arg); +} + + +/* REMOVE_EVENTS ioctl() */ +static int snd_seq_ioctl_remove_events(client_t * client, void __user *arg) +{ + snd_seq_remove_events_t info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + /* + * Input mostly not implemented XXX. + */ + if (info.remove_mode & SNDRV_SEQ_REMOVE_INPUT) { + /* + * No restrictions so for a user client we can clear + * the whole fifo + */ + if (client->type == USER_CLIENT) + snd_seq_fifo_clear(client->data.user.fifo); + } + + if (info.remove_mode & SNDRV_SEQ_REMOVE_OUTPUT) + snd_seq_queue_remove_cells(client->number, &info); + + return 0; +} + + +/* + * get subscription info + */ +static int snd_seq_ioctl_get_subscription(client_t *client, void __user *arg) +{ + int result; + client_t *sender = NULL; + client_port_t *sport = NULL; + snd_seq_port_subscribe_t subs; + subscribers_t *p; + + if (copy_from_user(&subs, arg, sizeof(subs))) + return -EFAULT; + + result = -EINVAL; + if ((sender = snd_seq_client_use_ptr(subs.sender.client)) == NULL) + goto __end; + if ((sport = snd_seq_port_use_ptr(sender, subs.sender.port)) == NULL) + goto __end; + p = snd_seq_port_get_subscription(&sport->c_src, &subs.dest); + if (p) { + result = 0; + subs = p->info; + } else + result = -ENOENT; + + __end: + if (sport) + snd_seq_port_unlock(sport); + if (sender) + snd_seq_client_unlock(sender); + if (result >= 0) { + if (copy_to_user(arg, &subs, sizeof(subs))) + return -EFAULT; + } + return result; +} + + +/* + * get subscription info - check only its presence + */ +static int snd_seq_ioctl_query_subs(client_t *client, void __user *arg) +{ + int result = -ENXIO; + client_t *cptr = NULL; + client_port_t *port = NULL; + snd_seq_query_subs_t subs; + port_subs_info_t *group; + struct list_head *p; + int i; + + if (copy_from_user(&subs, arg, sizeof(subs))) + return -EFAULT; + + if ((cptr = snd_seq_client_use_ptr(subs.root.client)) == NULL) + goto __end; + if ((port = snd_seq_port_use_ptr(cptr, subs.root.port)) == NULL) + goto __end; + + switch (subs.type) { + case SNDRV_SEQ_QUERY_SUBS_READ: + group = &port->c_src; + break; + case SNDRV_SEQ_QUERY_SUBS_WRITE: + group = &port->c_dest; + break; + default: + goto __end; + } + + down_read(&group->list_mutex); + /* search for the subscriber */ + subs.num_subs = group->count; + i = 0; + result = -ENOENT; + list_for_each(p, &group->list_head) { + if (i++ == subs.index) { + /* found! */ + subscribers_t *s; + if (subs.type == SNDRV_SEQ_QUERY_SUBS_READ) { + s = list_entry(p, subscribers_t, src_list); + subs.addr = s->info.dest; + } else { + s = list_entry(p, subscribers_t, dest_list); + subs.addr = s->info.sender; + } + subs.flags = s->info.flags; + subs.queue = s->info.queue; + result = 0; + break; + } + } + up_read(&group->list_mutex); + + __end: + if (port) + snd_seq_port_unlock(port); + if (cptr) + snd_seq_client_unlock(cptr); + if (result >= 0) { + if (copy_to_user(arg, &subs, sizeof(subs))) + return -EFAULT; + } + return result; +} + + +/* + * query next client + */ +static int snd_seq_ioctl_query_next_client(client_t *client, void __user *arg) +{ + client_t *cptr = NULL; + snd_seq_client_info_t info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + + /* search for next client */ + info.client++; + if (info.client < 0) + info.client = 0; + for (; info.client < SNDRV_SEQ_MAX_CLIENTS; info.client++) { + cptr = snd_seq_client_use_ptr(info.client); + if (cptr) + break; /* found */ + } + if (cptr == NULL) + return -ENOENT; + + get_client_info(cptr, &info); + snd_seq_client_unlock(cptr); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +/* + * query next port + */ +static int snd_seq_ioctl_query_next_port(client_t *client, void __user *arg) +{ + client_t *cptr; + client_port_t *port = NULL; + snd_seq_port_info_t info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + cptr = snd_seq_client_use_ptr(info.addr.client); + if (cptr == NULL) + return -ENXIO; + + /* search for next port */ + info.addr.port++; + port = snd_seq_port_query_nearest(cptr, &info); + if (port == NULL) { + snd_seq_client_unlock(cptr); + return -ENOENT; + } + + /* get port info */ + info.addr = port->addr; + snd_seq_get_port_info(port, &info); + snd_seq_port_unlock(port); + snd_seq_client_unlock(cptr); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +/* -------------------------------------------------------- */ + +static struct seq_ioctl_table { + unsigned int cmd; + int (*func)(client_t *client, void __user * arg); +} ioctl_tables[] = { + { SNDRV_SEQ_IOCTL_SYSTEM_INFO, snd_seq_ioctl_system_info }, + { SNDRV_SEQ_IOCTL_RUNNING_MODE, snd_seq_ioctl_running_mode }, + { SNDRV_SEQ_IOCTL_GET_CLIENT_INFO, snd_seq_ioctl_get_client_info }, + { SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, snd_seq_ioctl_set_client_info }, + { SNDRV_SEQ_IOCTL_CREATE_PORT, snd_seq_ioctl_create_port }, + { SNDRV_SEQ_IOCTL_DELETE_PORT, snd_seq_ioctl_delete_port }, + { SNDRV_SEQ_IOCTL_GET_PORT_INFO, snd_seq_ioctl_get_port_info }, + { SNDRV_SEQ_IOCTL_SET_PORT_INFO, snd_seq_ioctl_set_port_info }, + { SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, snd_seq_ioctl_subscribe_port }, + { SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, snd_seq_ioctl_unsubscribe_port }, + { SNDRV_SEQ_IOCTL_CREATE_QUEUE, snd_seq_ioctl_create_queue }, + { SNDRV_SEQ_IOCTL_DELETE_QUEUE, snd_seq_ioctl_delete_queue }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_INFO, snd_seq_ioctl_get_queue_info }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_INFO, snd_seq_ioctl_set_queue_info }, + { SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE, snd_seq_ioctl_get_named_queue }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS, snd_seq_ioctl_get_queue_status }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO, snd_seq_ioctl_get_queue_tempo }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO, snd_seq_ioctl_set_queue_tempo }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER, snd_seq_ioctl_get_queue_timer }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER, snd_seq_ioctl_set_queue_timer }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT, snd_seq_ioctl_get_queue_client }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT, snd_seq_ioctl_set_queue_client }, + { SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, snd_seq_ioctl_get_client_pool }, + { SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, snd_seq_ioctl_set_client_pool }, + { SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION, snd_seq_ioctl_get_subscription }, + { SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, snd_seq_ioctl_query_next_client }, + { SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, snd_seq_ioctl_query_next_port }, + { SNDRV_SEQ_IOCTL_REMOVE_EVENTS, snd_seq_ioctl_remove_events }, + { SNDRV_SEQ_IOCTL_QUERY_SUBS, snd_seq_ioctl_query_subs }, + { 0, NULL }, +}; + +static int snd_seq_do_ioctl(client_t *client, unsigned int cmd, void __user *arg) +{ + struct seq_ioctl_table *p; + + switch (cmd) { + case SNDRV_SEQ_IOCTL_PVERSION: + /* return sequencer version number */ + return put_user(SNDRV_SEQ_VERSION, (int __user *)arg) ? -EFAULT : 0; + case SNDRV_SEQ_IOCTL_CLIENT_ID: + /* return the id of this client */ + return put_user(client->number, (int __user *)arg) ? -EFAULT : 0; + } + + if (! arg) + return -EFAULT; + for (p = ioctl_tables; p->cmd; p++) { + if (p->cmd == cmd) + return p->func(client, arg); + } + snd_printd("seq unknown ioctl() 0x%x (type='%c', number=0x%2x)\n", + cmd, _IOC_TYPE(cmd), _IOC_NR(cmd)); + return -ENOTTY; +} + + +static long snd_seq_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + client_t *client = (client_t *) file->private_data; + + snd_assert(client != NULL, return -ENXIO); + + return snd_seq_do_ioctl(client, cmd, (void __user *) arg); +} + +#ifdef CONFIG_COMPAT +#include "seq_compat.c" +#else +#define snd_seq_ioctl_compat NULL +#endif + +/* -------------------------------------------------------- */ + + +/* exported to kernel modules */ +int snd_seq_create_kernel_client(snd_card_t *card, int client_index, snd_seq_client_callback_t * callback) +{ + client_t *client; + + snd_assert(! in_interrupt(), return -EBUSY); + + if (callback == NULL) + return -EINVAL; + if (card && client_index > 7) + return -EINVAL; + if (card == NULL && client_index > 63) + return -EINVAL; + if (card) + client_index += 64 + (card->number << 3); + + if (down_interruptible(®ister_mutex)) + return -ERESTARTSYS; + /* empty write queue as default */ + client = seq_create_client1(client_index, 0); + if (client == NULL) { + up(®ister_mutex); + return -EBUSY; /* failure code */ + } + usage_alloc(&client_usage, 1); + + client->accept_input = callback->allow_output; + client->accept_output = callback->allow_input; + + /* fill client data */ + client->data.kernel.card = card; + client->data.kernel.private_data = callback->private_data; + sprintf(client->name, "Client-%d", client->number); + + client->type = KERNEL_CLIENT; + up(®ister_mutex); + + /* make others aware this new client */ + snd_seq_system_client_ev_client_start(client->number); + + /* return client number to caller */ + return client->number; +} + +/* exported to kernel modules */ +int snd_seq_delete_kernel_client(int client) +{ + client_t *ptr; + + snd_assert(! in_interrupt(), return -EBUSY); + + ptr = clientptr(client); + if (ptr == NULL) + return -EINVAL; + + seq_free_client(ptr); + kfree(ptr); + return 0; +} + + +/* skeleton to enqueue event, called from snd_seq_kernel_client_enqueue + * and snd_seq_kernel_client_enqueue_blocking + */ +static int kernel_client_enqueue(int client, snd_seq_event_t *ev, + struct file *file, int blocking, + int atomic, int hop) +{ + client_t *cptr; + int result; + + snd_assert(ev != NULL, return -EINVAL); + + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; /* ignore this */ + if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) + return -EINVAL; /* quoted events can't be enqueued */ + + /* fill in client number */ + ev->source.client = client; + + if (check_event_type_and_length(ev)) + return -EINVAL; + + cptr = snd_seq_client_use_ptr(client); + if (cptr == NULL) + return -EINVAL; + + if (! cptr->accept_output) + result = -EPERM; + else /* send it */ + result = snd_seq_client_enqueue_event(cptr, ev, file, blocking, atomic, hop); + + snd_seq_client_unlock(cptr); + return result; +} + +/* + * exported, called by kernel clients to enqueue events (w/o blocking) + * + * RETURN VALUE: zero if succeed, negative if error + */ +int snd_seq_kernel_client_enqueue(int client, snd_seq_event_t * ev, + int atomic, int hop) +{ + return kernel_client_enqueue(client, ev, NULL, 0, atomic, hop); +} + +/* + * exported, called by kernel clients to enqueue events (with blocking) + * + * RETURN VALUE: zero if succeed, negative if error + */ +int snd_seq_kernel_client_enqueue_blocking(int client, snd_seq_event_t * ev, + struct file *file, + int atomic, int hop) +{ + return kernel_client_enqueue(client, ev, file, 1, atomic, hop); +} + + +/* + * exported, called by kernel clients to dispatch events directly to other + * clients, bypassing the queues. Event time-stamp will be updated. + * + * RETURN VALUE: negative = delivery failed, + * zero, or positive: the number of delivered events + */ +int snd_seq_kernel_client_dispatch(int client, snd_seq_event_t * ev, + int atomic, int hop) +{ + client_t *cptr; + int result; + + snd_assert(ev != NULL, return -EINVAL); + + /* fill in client number */ + ev->queue = SNDRV_SEQ_QUEUE_DIRECT; + ev->source.client = client; + + if (check_event_type_and_length(ev)) + return -EINVAL; + + cptr = snd_seq_client_use_ptr(client); + if (cptr == NULL) + return -EINVAL; + + if (!cptr->accept_output) + result = -EPERM; + else + result = snd_seq_deliver_event(cptr, ev, atomic, hop); + + snd_seq_client_unlock(cptr); + return result; +} + + +/* + * exported, called by kernel clients to perform same functions as with + * userland ioctl() + */ +int snd_seq_kernel_client_ctl(int clientid, unsigned int cmd, void *arg) +{ + client_t *client; + mm_segment_t fs; + int result; + + client = clientptr(clientid); + if (client == NULL) + return -ENXIO; + fs = snd_enter_user(); + result = snd_seq_do_ioctl(client, cmd, (void __user *)arg); + snd_leave_user(fs); + return result; +} + + +/* exported (for OSS emulator) */ +int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait) +{ + client_t *client; + + client = clientptr(clientid); + if (client == NULL) + return -ENXIO; + + if (! snd_seq_write_pool_allocated(client)) + return 1; + if (snd_seq_pool_poll_wait(client->pool, file, wait)) + return 1; + return 0; +} + +/*---------------------------------------------------------------------------*/ + +/* + * /proc interface + */ +static void snd_seq_info_dump_subscribers(snd_info_buffer_t *buffer, port_subs_info_t *group, int is_src, char *msg) +{ + struct list_head *p; + subscribers_t *s; + int count = 0; + + down_read(&group->list_mutex); + if (list_empty(&group->list_head)) { + up_read(&group->list_mutex); + return; + } + snd_iprintf(buffer, msg); + list_for_each(p, &group->list_head) { + if (is_src) + s = list_entry(p, subscribers_t, src_list); + else + s = list_entry(p, subscribers_t, dest_list); + if (count++) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d:%d", + is_src ? s->info.dest.client : s->info.sender.client, + is_src ? s->info.dest.port : s->info.sender.port); + if (s->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP) + snd_iprintf(buffer, "[%c:%d]", ((s->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL) ? 'r' : 't'), s->info.queue); + if (group->exclusive) + snd_iprintf(buffer, "[ex]"); + } + up_read(&group->list_mutex); + snd_iprintf(buffer, "\n"); +} + +#define FLAG_PERM_RD(perm) ((perm) & SNDRV_SEQ_PORT_CAP_READ ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_READ ? 'R' : 'r') : '-') +#define FLAG_PERM_WR(perm) ((perm) & SNDRV_SEQ_PORT_CAP_WRITE ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_WRITE ? 'W' : 'w') : '-') +#define FLAG_PERM_EX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_NO_EXPORT ? '-' : 'e') + +#define FLAG_PERM_DUPLEX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_DUPLEX ? 'X' : '-') + +static void snd_seq_info_dump_ports(snd_info_buffer_t *buffer, client_t *client) +{ + struct list_head *l; + + down(&client->ports_mutex); + list_for_each(l, &client->ports_list_head) { + client_port_t *p = list_entry(l, client_port_t, list); + snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c)\n", + p->addr.port, p->name, + FLAG_PERM_RD(p->capability), + FLAG_PERM_WR(p->capability), + FLAG_PERM_EX(p->capability), + FLAG_PERM_DUPLEX(p->capability)); + snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, " Connecting To: "); + snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, " Connected From: "); + } + up(&client->ports_mutex); +} + + +/* exported to seq_info.c */ +void snd_seq_info_clients_read(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + extern void snd_seq_info_pool(snd_info_buffer_t * buffer, pool_t * pool, char *space); + int c; + client_t *client; + + snd_iprintf(buffer, "Client info\n"); + snd_iprintf(buffer, " cur clients : %d\n", client_usage.cur); + snd_iprintf(buffer, " peak clients : %d\n", client_usage.peak); + snd_iprintf(buffer, " max clients : %d\n", SNDRV_SEQ_MAX_CLIENTS); + snd_iprintf(buffer, "\n"); + + /* list the client table */ + for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) { + client = snd_seq_client_use_ptr(c); + if (client == NULL) + continue; + if (client->type == NO_CLIENT) { + snd_seq_client_unlock(client); + continue; + } + + snd_iprintf(buffer, "Client %3d : \"%s\" [%s]\n", + c, client->name, + client->type == USER_CLIENT ? "User" : "Kernel"); + snd_seq_info_dump_ports(buffer, client); + if (snd_seq_write_pool_allocated(client)) { + snd_iprintf(buffer, " Output pool :\n"); + snd_seq_info_pool(buffer, client->pool, " "); + } + if (client->type == USER_CLIENT && client->data.user.fifo && + client->data.user.fifo->pool) { + snd_iprintf(buffer, " Input pool :\n"); + snd_seq_info_pool(buffer, client->data.user.fifo->pool, " "); + } + snd_seq_client_unlock(client); + } +} + + +/*---------------------------------------------------------------------------*/ + + +/* + * REGISTRATION PART + */ + +static struct file_operations snd_seq_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_seq_read, + .write = snd_seq_write, + .open = snd_seq_open, + .release = snd_seq_release, + .poll = snd_seq_poll, + .unlocked_ioctl = snd_seq_ioctl, + .compat_ioctl = snd_seq_ioctl_compat, +}; + +static snd_minor_t snd_seq_reg = +{ + .comment = "sequencer", + .f_ops = &snd_seq_f_ops, +}; + + +/* + * register sequencer device + */ +int __init snd_sequencer_device_init(void) +{ + int err; + + if (down_interruptible(®ister_mutex)) + return -ERESTARTSYS; + + if ((err = snd_register_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0, &snd_seq_reg, "seq")) < 0) { + up(®ister_mutex); + return err; + } + + up(®ister_mutex); + + return 0; +} + + + +/* + * unregister sequencer device + */ +void __exit snd_sequencer_device_done(void) +{ + snd_unregister_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0); +} diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h new file mode 100644 index 000000000000..3715c36183d3 --- /dev/null +++ b/sound/core/seq/seq_clientmgr.h @@ -0,0 +1,104 @@ +/* + * ALSA sequencer Client Manager + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_CLIENTMGR_H +#define __SND_SEQ_CLIENTMGR_H + +#include <sound/seq_kernel.h> +#include <linux/bitops.h> +#include "seq_fifo.h" +#include "seq_ports.h" +#include "seq_lock.h" + + +/* client manager */ + +struct _snd_seq_user_client { + struct file *file; /* file struct of client */ + /* ... */ + + /* fifo */ + fifo_t *fifo; /* queue for incoming events */ + int fifo_pool_size; +}; + +struct _snd_seq_kernel_client { + snd_card_t *card; + /* pointer to client functions */ + void *private_data; /* private data for client */ + /* ... */ +}; + + +struct _snd_seq_client { + snd_seq_client_type_t type; + unsigned int accept_input: 1, + accept_output: 1; + char name[64]; /* client name */ + int number; /* client number */ + unsigned int filter; /* filter flags */ + DECLARE_BITMAP(event_filter, 256); + snd_use_lock_t use_lock; + int event_lost; + /* ports */ + int num_ports; /* number of ports */ + struct list_head ports_list_head; + rwlock_t ports_lock; + struct semaphore ports_mutex; + int convert32; /* convert 32->64bit */ + + /* output pool */ + pool_t *pool; /* memory pool for this client */ + + union { + user_client_t user; + kernel_client_t kernel; + } data; +}; + +/* usage statistics */ +typedef struct { + int cur; + int peak; +} usage_t; + + +extern int client_init_data(void); +extern int snd_sequencer_device_init(void); +extern void snd_sequencer_device_done(void); + +/* get locked pointer to client */ +extern client_t *snd_seq_client_use_ptr(int clientid); + +/* unlock pointer to client */ +#define snd_seq_client_unlock(client) snd_use_lock_free(&(client)->use_lock) + +/* dispatch event to client(s) */ +extern int snd_seq_dispatch_event(snd_seq_event_cell_t *cell, int atomic, int hop); + +/* exported to other modules */ +extern int snd_seq_register_kernel_client(snd_seq_client_callback_t *callback, void *private_data); +extern int snd_seq_unregister_kernel_client(int client); +extern int snd_seq_kernel_client_enqueue(int client, snd_seq_event_t *ev, int atomic, int hop); +int snd_seq_kernel_client_enqueue_blocking(int client, snd_seq_event_t * ev, struct file *file, int atomic, int hop); +int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait); +int snd_seq_client_notify_subscription(int client, int port, snd_seq_port_subscribe_t *info, int evtype); + +#endif diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c new file mode 100644 index 000000000000..902ad8b0c355 --- /dev/null +++ b/sound/core/seq/seq_compat.c @@ -0,0 +1,137 @@ +/* + * 32bit -> 64bit ioctl wrapper for sequencer API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This file included from seq.c */ + +#include <linux/compat.h> + +struct sndrv_seq_port_info32 { + struct sndrv_seq_addr addr; /* client/port numbers */ + char name[64]; /* port name */ + + u32 capability; /* port capability bits */ + u32 type; /* port type bits */ + s32 midi_channels; /* channels per MIDI port */ + s32 midi_voices; /* voices per MIDI port */ + s32 synth_voices; /* voices per SYNTH port */ + + s32 read_use; /* R/O: subscribers for output (from this port) */ + s32 write_use; /* R/O: subscribers for input (to this port) */ + + u32 kernel; /* reserved for kernel use (must be NULL) */ + u32 flags; /* misc. conditioning */ + unsigned char time_queue; /* queue # for timestamping */ + char reserved[59]; /* for future use */ +}; + +static int snd_seq_call_port_info_ioctl(client_t *client, unsigned int cmd, + struct sndrv_seq_port_info32 __user *data32) +{ + int err = -EFAULT; + snd_seq_port_info_t *data; + mm_segment_t fs; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (! data) + return -ENOMEM; + + if (copy_from_user(data, data32, sizeof(*data32)) || + get_user(data->flags, &data32->flags) || + get_user(data->time_queue, &data32->time_queue)) + goto error; + data->kernel = NULL; + + fs = snd_enter_user(); + err = snd_seq_do_ioctl(client, cmd, data); + snd_leave_user(fs); + if (err < 0) + goto error; + + if (copy_to_user(data32, data, sizeof(*data32)) || + put_user(data->flags, &data32->flags) || + put_user(data->time_queue, &data32->time_queue)) + err = -EFAULT; + + error: + kfree(data); + return err; +} + + + +/* + */ + +enum { + SNDRV_SEQ_IOCTL_CREATE_PORT32 = _IOWR('S', 0x20, struct sndrv_seq_port_info32), + SNDRV_SEQ_IOCTL_DELETE_PORT32 = _IOW ('S', 0x21, struct sndrv_seq_port_info32), + SNDRV_SEQ_IOCTL_GET_PORT_INFO32 = _IOWR('S', 0x22, struct sndrv_seq_port_info32), + SNDRV_SEQ_IOCTL_SET_PORT_INFO32 = _IOW ('S', 0x23, struct sndrv_seq_port_info32), + SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32 = _IOWR('S', 0x52, struct sndrv_seq_port_info32), +}; + +static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + client_t *client = (client_t *) file->private_data; + void __user *argp = compat_ptr(arg); + + snd_assert(client != NULL, return -ENXIO); + + switch (cmd) { + case SNDRV_SEQ_IOCTL_PVERSION: + case SNDRV_SEQ_IOCTL_CLIENT_ID: + case SNDRV_SEQ_IOCTL_SYSTEM_INFO: + case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO: + case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO: + case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT: + case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT: + case SNDRV_SEQ_IOCTL_CREATE_QUEUE: + case SNDRV_SEQ_IOCTL_DELETE_QUEUE: + case SNDRV_SEQ_IOCTL_GET_QUEUE_INFO: + case SNDRV_SEQ_IOCTL_SET_QUEUE_INFO: + case SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE: + case SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS: + case SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO: + case SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO: + case SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER: + case SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER: + case SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT: + case SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT: + case SNDRV_SEQ_IOCTL_GET_CLIENT_POOL: + case SNDRV_SEQ_IOCTL_SET_CLIENT_POOL: + case SNDRV_SEQ_IOCTL_REMOVE_EVENTS: + case SNDRV_SEQ_IOCTL_QUERY_SUBS: + case SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION: + case SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT: + case SNDRV_SEQ_IOCTL_RUNNING_MODE: + return snd_seq_do_ioctl(client, cmd, argp); + case SNDRV_SEQ_IOCTL_CREATE_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, argp); + case SNDRV_SEQ_IOCTL_DELETE_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_DELETE_PORT, argp); + case SNDRV_SEQ_IOCTL_GET_PORT_INFO32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_GET_PORT_INFO, argp); + case SNDRV_SEQ_IOCTL_SET_PORT_INFO32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_SET_PORT_INFO, argp); + case SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, argp); + } + return -ENOIOCTLCMD; +} diff --git a/sound/core/seq/seq_device.c b/sound/core/seq/seq_device.c new file mode 100644 index 000000000000..4d80f39612e8 --- /dev/null +++ b/sound/core/seq/seq_device.c @@ -0,0 +1,575 @@ +/* + * ALSA sequencer device management + * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + *---------------------------------------------------------------- + * + * This device handler separates the card driver module from sequencer + * stuff (sequencer core, synth drivers, etc), so that user can avoid + * to spend unnecessary resources e.g. if he needs only listening to + * MP3s. + * + * The card (or lowlevel) driver creates a sequencer device entry + * via snd_seq_device_new(). This is an entry pointer to communicate + * with the sequencer device "driver", which is involved with the + * actual part to communicate with the sequencer core. + * Each sequencer device entry has an id string and the corresponding + * driver with the same id is loaded when required. For example, + * lowlevel codes to access emu8000 chip on sbawe card are included in + * emu8000-synth module. To activate this module, the hardware + * resources like i/o port are passed via snd_seq_device argument. + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <sound/core.h> +#include <sound/info.h> +#include <sound/seq_device.h> +#include <sound/seq_kernel.h> +#include <sound/initval.h> +#include <linux/kmod.h> +#include <linux/slab.h> + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("ALSA sequencer device management"); +MODULE_LICENSE("GPL"); + +/* + * driver list + */ +typedef struct ops_list ops_list_t; + +/* driver state */ +#define DRIVER_EMPTY 0 +#define DRIVER_LOADED (1<<0) +#define DRIVER_REQUESTED (1<<1) +#define DRIVER_LOCKED (1<<2) + +struct ops_list { + char id[ID_LEN]; /* driver id */ + int driver; /* driver state */ + int used; /* reference counter */ + int argsize; /* argument size */ + + /* operators */ + snd_seq_dev_ops_t ops; + + /* registred devices */ + struct list_head dev_list; /* list of devices */ + int num_devices; /* number of associated devices */ + int num_init_devices; /* number of initialized devices */ + struct semaphore reg_mutex; + + struct list_head list; /* next driver */ +}; + + +static LIST_HEAD(opslist); +static int num_ops; +static DECLARE_MUTEX(ops_mutex); +static snd_info_entry_t *info_entry = NULL; + +/* + * prototypes + */ +static int snd_seq_device_free(snd_seq_device_t *dev); +static int snd_seq_device_dev_free(snd_device_t *device); +static int snd_seq_device_dev_register(snd_device_t *device); +static int snd_seq_device_dev_disconnect(snd_device_t *device); +static int snd_seq_device_dev_unregister(snd_device_t *device); + +static int init_device(snd_seq_device_t *dev, ops_list_t *ops); +static int free_device(snd_seq_device_t *dev, ops_list_t *ops); +static ops_list_t *find_driver(char *id, int create_if_empty); +static ops_list_t *create_driver(char *id); +static void unlock_driver(ops_list_t *ops); +static void remove_drivers(void); + +/* + * show all drivers and their status + */ + +static void snd_seq_device_info(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + struct list_head *head; + + down(&ops_mutex); + list_for_each(head, &opslist) { + ops_list_t *ops = list_entry(head, ops_list_t, list); + snd_iprintf(buffer, "snd-%s%s%s%s,%d\n", + ops->id, + ops->driver & DRIVER_LOADED ? ",loaded" : (ops->driver == DRIVER_EMPTY ? ",empty" : ""), + ops->driver & DRIVER_REQUESTED ? ",requested" : "", + ops->driver & DRIVER_LOCKED ? ",locked" : "", + ops->num_devices); + } + up(&ops_mutex); +} + +/* + * load all registered drivers (called from seq_clientmgr.c) + */ + +#ifdef CONFIG_KMOD +/* avoid auto-loading during module_init() */ +static int snd_seq_in_init; +void snd_seq_autoload_lock(void) +{ + snd_seq_in_init++; +} + +void snd_seq_autoload_unlock(void) +{ + snd_seq_in_init--; +} +#endif + +void snd_seq_device_load_drivers(void) +{ +#ifdef CONFIG_KMOD + struct list_head *head; + + /* Calling request_module during module_init() + * may cause blocking. + */ + if (snd_seq_in_init) + return; + + if (! current->fs->root) + return; + + down(&ops_mutex); + list_for_each(head, &opslist) { + ops_list_t *ops = list_entry(head, ops_list_t, list); + if (! (ops->driver & DRIVER_LOADED) && + ! (ops->driver & DRIVER_REQUESTED)) { + ops->used++; + up(&ops_mutex); + ops->driver |= DRIVER_REQUESTED; + request_module("snd-%s", ops->id); + down(&ops_mutex); + ops->used--; + } + } + up(&ops_mutex); +#endif +} + +/* + * register a sequencer device + * card = card info (NULL allowed) + * device = device number (if any) + * id = id of driver + * result = return pointer (NULL allowed if unnecessary) + */ +int snd_seq_device_new(snd_card_t *card, int device, char *id, int argsize, + snd_seq_device_t **result) +{ + snd_seq_device_t *dev; + ops_list_t *ops; + int err; + static snd_device_ops_t dops = { + .dev_free = snd_seq_device_dev_free, + .dev_register = snd_seq_device_dev_register, + .dev_disconnect = snd_seq_device_dev_disconnect, + .dev_unregister = snd_seq_device_dev_unregister + }; + + if (result) + *result = NULL; + + snd_assert(id != NULL, return -EINVAL); + + ops = find_driver(id, 1); + if (ops == NULL) + return -ENOMEM; + + dev = kcalloc(1, sizeof(*dev)*2 + argsize, GFP_KERNEL); + if (dev == NULL) { + unlock_driver(ops); + return -ENOMEM; + } + + /* set up device info */ + dev->card = card; + dev->device = device; + strlcpy(dev->id, id, sizeof(dev->id)); + dev->argsize = argsize; + dev->status = SNDRV_SEQ_DEVICE_FREE; + + /* add this device to the list */ + down(&ops->reg_mutex); + list_add_tail(&dev->list, &ops->dev_list); + ops->num_devices++; + up(&ops->reg_mutex); + + unlock_driver(ops); + + if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) { + snd_seq_device_free(dev); + return err; + } + + if (result) + *result = dev; + + return 0; +} + +/* + * free the existing device + */ +static int snd_seq_device_free(snd_seq_device_t *dev) +{ + ops_list_t *ops; + + snd_assert(dev != NULL, return -EINVAL); + + ops = find_driver(dev->id, 0); + if (ops == NULL) + return -ENXIO; + + /* remove the device from the list */ + down(&ops->reg_mutex); + list_del(&dev->list); + ops->num_devices--; + up(&ops->reg_mutex); + + free_device(dev, ops); + if (dev->private_free) + dev->private_free(dev); + kfree(dev); + + unlock_driver(ops); + + return 0; +} + +static int snd_seq_device_dev_free(snd_device_t *device) +{ + snd_seq_device_t *dev = device->device_data; + return snd_seq_device_free(dev); +} + +/* + * register the device + */ +static int snd_seq_device_dev_register(snd_device_t *device) +{ + snd_seq_device_t *dev = device->device_data; + ops_list_t *ops; + + ops = find_driver(dev->id, 0); + if (ops == NULL) + return -ENOENT; + + /* initialize this device if the corresponding driver was + * already loaded + */ + if (ops->driver & DRIVER_LOADED) + init_device(dev, ops); + + unlock_driver(ops); + return 0; +} + +/* + * disconnect the device + */ +static int snd_seq_device_dev_disconnect(snd_device_t *device) +{ + snd_seq_device_t *dev = device->device_data; + ops_list_t *ops; + + ops = find_driver(dev->id, 0); + if (ops == NULL) + return -ENOENT; + + free_device(dev, ops); + + unlock_driver(ops); + return 0; +} + +/* + * unregister the existing device + */ +static int snd_seq_device_dev_unregister(snd_device_t *device) +{ + snd_seq_device_t *dev = device->device_data; + return snd_seq_device_free(dev); +} + +/* + * register device driver + * id = driver id + * entry = driver operators - duplicated to each instance + */ +int snd_seq_device_register_driver(char *id, snd_seq_dev_ops_t *entry, int argsize) +{ + struct list_head *head; + ops_list_t *ops; + + if (id == NULL || entry == NULL || + entry->init_device == NULL || entry->free_device == NULL) + return -EINVAL; + + snd_seq_autoload_lock(); + ops = find_driver(id, 1); + if (ops == NULL) { + snd_seq_autoload_unlock(); + return -ENOMEM; + } + if (ops->driver & DRIVER_LOADED) { + snd_printk(KERN_WARNING "driver_register: driver '%s' already exists\n", id); + unlock_driver(ops); + snd_seq_autoload_unlock(); + return -EBUSY; + } + + down(&ops->reg_mutex); + /* copy driver operators */ + ops->ops = *entry; + ops->driver |= DRIVER_LOADED; + ops->argsize = argsize; + + /* initialize existing devices if necessary */ + list_for_each(head, &ops->dev_list) { + snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list); + init_device(dev, ops); + } + up(&ops->reg_mutex); + + unlock_driver(ops); + snd_seq_autoload_unlock(); + + return 0; +} + + +/* + * create driver record + */ +static ops_list_t * create_driver(char *id) +{ + ops_list_t *ops; + + ops = kmalloc(sizeof(*ops), GFP_KERNEL); + if (ops == NULL) + return ops; + memset(ops, 0, sizeof(*ops)); + + /* set up driver entry */ + strlcpy(ops->id, id, sizeof(ops->id)); + init_MUTEX(&ops->reg_mutex); + ops->driver = DRIVER_EMPTY; + INIT_LIST_HEAD(&ops->dev_list); + /* lock this instance */ + ops->used = 1; + + /* register driver entry */ + down(&ops_mutex); + list_add_tail(&ops->list, &opslist); + num_ops++; + up(&ops_mutex); + + return ops; +} + + +/* + * unregister the specified driver + */ +int snd_seq_device_unregister_driver(char *id) +{ + struct list_head *head; + ops_list_t *ops; + + ops = find_driver(id, 0); + if (ops == NULL) + return -ENXIO; + if (! (ops->driver & DRIVER_LOADED) || + (ops->driver & DRIVER_LOCKED)) { + snd_printk(KERN_ERR "driver_unregister: cannot unload driver '%s': status=%x\n", id, ops->driver); + unlock_driver(ops); + return -EBUSY; + } + + /* close and release all devices associated with this driver */ + down(&ops->reg_mutex); + ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */ + list_for_each(head, &ops->dev_list) { + snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list); + free_device(dev, ops); + } + + ops->driver = 0; + if (ops->num_init_devices > 0) + snd_printk(KERN_ERR "free_driver: init_devices > 0!! (%d)\n", ops->num_init_devices); + up(&ops->reg_mutex); + + unlock_driver(ops); + + /* remove empty driver entries */ + remove_drivers(); + + return 0; +} + + +/* + * remove empty driver entries + */ +static void remove_drivers(void) +{ + struct list_head *head; + + down(&ops_mutex); + head = opslist.next; + while (head != &opslist) { + ops_list_t *ops = list_entry(head, ops_list_t, list); + if (! (ops->driver & DRIVER_LOADED) && + ops->used == 0 && ops->num_devices == 0) { + head = head->next; + list_del(&ops->list); + kfree(ops); + num_ops--; + } else + head = head->next; + } + up(&ops_mutex); +} + +/* + * initialize the device - call init_device operator + */ +static int init_device(snd_seq_device_t *dev, ops_list_t *ops) +{ + if (! (ops->driver & DRIVER_LOADED)) + return 0; /* driver is not loaded yet */ + if (dev->status != SNDRV_SEQ_DEVICE_FREE) + return 0; /* already initialized */ + if (ops->argsize != dev->argsize) { + snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", dev->name, ops->id, ops->argsize, dev->argsize); + return -EINVAL; + } + if (ops->ops.init_device(dev) >= 0) { + dev->status = SNDRV_SEQ_DEVICE_REGISTERED; + ops->num_init_devices++; + } else { + snd_printk(KERN_ERR "init_device failed: %s: %s\n", dev->name, dev->id); + } + + return 0; +} + +/* + * release the device - call free_device operator + */ +static int free_device(snd_seq_device_t *dev, ops_list_t *ops) +{ + int result; + + if (! (ops->driver & DRIVER_LOADED)) + return 0; /* driver is not loaded yet */ + if (dev->status != SNDRV_SEQ_DEVICE_REGISTERED) + return 0; /* not registered */ + if (ops->argsize != dev->argsize) { + snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", dev->name, ops->id, ops->argsize, dev->argsize); + return -EINVAL; + } + if ((result = ops->ops.free_device(dev)) >= 0 || result == -ENXIO) { + dev->status = SNDRV_SEQ_DEVICE_FREE; + dev->driver_data = NULL; + ops->num_init_devices--; + } else { + snd_printk(KERN_ERR "free_device failed: %s: %s\n", dev->name, dev->id); + } + + return 0; +} + +/* + * find the matching driver with given id + */ +static ops_list_t * find_driver(char *id, int create_if_empty) +{ + struct list_head *head; + + down(&ops_mutex); + list_for_each(head, &opslist) { + ops_list_t *ops = list_entry(head, ops_list_t, list); + if (strcmp(ops->id, id) == 0) { + ops->used++; + up(&ops_mutex); + return ops; + } + } + up(&ops_mutex); + if (create_if_empty) + return create_driver(id); + return NULL; +} + +static void unlock_driver(ops_list_t *ops) +{ + down(&ops_mutex); + ops->used--; + up(&ops_mutex); +} + + +/* + * module part + */ + +static int __init alsa_seq_device_init(void) +{ + info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", snd_seq_root); + if (info_entry == NULL) + return -ENOMEM; + info_entry->content = SNDRV_INFO_CONTENT_TEXT; + info_entry->c.text.read_size = 2048; + info_entry->c.text.read = snd_seq_device_info; + if (snd_info_register(info_entry) < 0) { + snd_info_free_entry(info_entry); + return -ENOMEM; + } + return 0; +} + +static void __exit alsa_seq_device_exit(void) +{ + remove_drivers(); + snd_info_unregister(info_entry); + if (num_ops) + snd_printk(KERN_ERR "drivers not released (%d)\n", num_ops); +} + +module_init(alsa_seq_device_init) +module_exit(alsa_seq_device_exit) + +EXPORT_SYMBOL(snd_seq_device_load_drivers); +EXPORT_SYMBOL(snd_seq_device_new); +EXPORT_SYMBOL(snd_seq_device_register_driver); +EXPORT_SYMBOL(snd_seq_device_unregister_driver); +#ifdef CONFIG_KMOD +EXPORT_SYMBOL(snd_seq_autoload_lock); +EXPORT_SYMBOL(snd_seq_autoload_unlock); +#endif diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c new file mode 100644 index 000000000000..e88967c5b93d --- /dev/null +++ b/sound/core/seq/seq_dummy.c @@ -0,0 +1,273 @@ +/* + * ALSA sequencer MIDI-through client + * Copyright (c) 1999-2000 by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include "seq_clientmgr.h" +#include <sound/initval.h> +#include <sound/asoundef.h> + +/* + + Sequencer MIDI-through client + + This gives a simple midi-through client. All the normal input events + are redirected to output port immediately. + The routing can be done via aconnect program in alsa-utils. + + Each client has a static client number 62 (= SNDRV_SEQ_CLIENT_DUMMY). + If you want to auto-load this module, you may add the following alias + in your /etc/conf.modules file. + + alias snd-seq-client-62 snd-seq-dummy + + The module is loaded on demand for client 62, or /proc/asound/seq/ + is accessed. If you don't need this module to be loaded, alias + snd-seq-client-62 as "off". This will help modprobe. + + The number of ports to be created can be specified via the module + parameter "ports". For example, to create four ports, add the + following option in /etc/modprobe.conf: + + option snd-seq-dummy ports=4 + + The modle option "duplex=1" enables duplex operation to the port. + In duplex mode, a pair of ports are created instead of single port, + and events are tunneled between pair-ports. For example, input to + port A is sent to output port of another port B and vice versa. + In duplex mode, each port has DUPLEX capability. + + */ + + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("ALSA sequencer MIDI-through client"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY)); + +static int ports = 1; +static int duplex = 0; + +module_param(ports, int, 0444); +MODULE_PARM_DESC(ports, "number of ports to be created"); +module_param(duplex, bool, 0444); +MODULE_PARM_DESC(duplex, "create DUPLEX ports"); + +typedef struct snd_seq_dummy_port { + int client; + int port; + int duplex; + int connect; +} snd_seq_dummy_port_t; + +static int my_client = -1; + +/* + * unuse callback - send ALL_SOUNDS_OFF and RESET_CONTROLLERS events + * to subscribers. + * Note: this callback is called only after all subscribers are removed. + */ +static int +dummy_unuse(void *private_data, snd_seq_port_subscribe_t *info) +{ + snd_seq_dummy_port_t *p; + int i; + snd_seq_event_t ev; + + p = private_data; + memset(&ev, 0, sizeof(ev)); + if (p->duplex) + ev.source.port = p->connect; + else + ev.source.port = p->port; + ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + ev.type = SNDRV_SEQ_EVENT_CONTROLLER; + for (i = 0; i < 16; i++) { + ev.data.control.channel = i; + ev.data.control.param = MIDI_CTL_ALL_SOUNDS_OFF; + snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0); + ev.data.control.param = MIDI_CTL_RESET_CONTROLLERS; + snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0); + } + return 0; +} + +/* + * event input callback - just redirect events to subscribers + */ +static int +dummy_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop) +{ + snd_seq_dummy_port_t *p; + snd_seq_event_t tmpev; + + p = private_data; + if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM || + ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) + return 0; /* ignore system messages */ + tmpev = *ev; + if (p->duplex) + tmpev.source.port = p->connect; + else + tmpev.source.port = p->port; + tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop); +} + +/* + * free_private callback + */ +static void +dummy_free(void *private_data) +{ + snd_seq_dummy_port_t *p; + + p = private_data; + kfree(p); +} + +/* + * create a port + */ +static snd_seq_dummy_port_t __init * +create_port(int idx, int type) +{ + snd_seq_port_info_t pinfo; + snd_seq_port_callback_t pcb; + snd_seq_dummy_port_t *rec; + + if ((rec = kcalloc(1, sizeof(*rec), GFP_KERNEL)) == NULL) + return NULL; + + rec->client = my_client; + rec->duplex = duplex; + rec->connect = 0; + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.addr.client = my_client; + if (duplex) + sprintf(pinfo.name, "Midi Through Port-%d:%c", idx, + (type ? 'B' : 'A')); + else + sprintf(pinfo.name, "Midi Through Port-%d", idx); + pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if (duplex) + pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC; + memset(&pcb, 0, sizeof(pcb)); + pcb.owner = THIS_MODULE; + pcb.unuse = dummy_unuse; + pcb.event_input = dummy_input; + pcb.private_free = dummy_free; + pcb.private_data = rec; + pinfo.kernel = &pcb; + if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) { + kfree(rec); + return NULL; + } + rec->port = pinfo.addr.port; + return rec; +} + +/* + * register client and create ports + */ +static int __init +register_client(void) +{ + snd_seq_client_callback_t cb; + snd_seq_client_info_t cinfo; + snd_seq_dummy_port_t *rec1, *rec2; + int i; + + if (ports < 1) { + snd_printk(KERN_ERR "invalid number of ports %d\n", ports); + return -EINVAL; + } + + /* create client */ + memset(&cb, 0, sizeof(cb)); + cb.allow_input = 1; + cb.allow_output = 1; + my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY, &cb); + if (my_client < 0) + return my_client; + + /* set client name */ + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.client = my_client; + cinfo.type = KERNEL_CLIENT; + strcpy(cinfo.name, "Midi Through"); + snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo); + + /* create ports */ + for (i = 0; i < ports; i++) { + rec1 = create_port(i, 0); + if (rec1 == NULL) { + snd_seq_delete_kernel_client(my_client); + return -ENOMEM; + } + if (duplex) { + rec2 = create_port(i, 1); + if (rec2 == NULL) { + snd_seq_delete_kernel_client(my_client); + return -ENOMEM; + } + rec1->connect = rec2->port; + rec2->connect = rec1->port; + } + } + + return 0; +} + +/* + * delete client if exists + */ +static void __exit +delete_client(void) +{ + if (my_client >= 0) + snd_seq_delete_kernel_client(my_client); +} + +/* + * Init part + */ + +static int __init alsa_seq_dummy_init(void) +{ + int err; + snd_seq_autoload_lock(); + err = register_client(); + snd_seq_autoload_unlock(); + return err; +} + +static void __exit alsa_seq_dummy_exit(void) +{ + delete_client(); +} + +module_init(alsa_seq_dummy_init) +module_exit(alsa_seq_dummy_exit) diff --git a/sound/core/seq/seq_fifo.c b/sound/core/seq/seq_fifo.c new file mode 100644 index 000000000000..3b7647ca7ad9 --- /dev/null +++ b/sound/core/seq/seq_fifo.c @@ -0,0 +1,264 @@ +/* + * ALSA sequencer FIFO + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <sound/core.h> +#include <linux/slab.h> +#include "seq_fifo.h" +#include "seq_lock.h" + + +/* FIFO */ + +/* create new fifo */ +fifo_t *snd_seq_fifo_new(int poolsize) +{ + fifo_t *f; + + f = kcalloc(1, sizeof(*f), GFP_KERNEL); + if (f == NULL) { + snd_printd("malloc failed for snd_seq_fifo_new() \n"); + return NULL; + } + + f->pool = snd_seq_pool_new(poolsize); + if (f->pool == NULL) { + kfree(f); + return NULL; + } + if (snd_seq_pool_init(f->pool) < 0) { + snd_seq_pool_delete(&f->pool); + kfree(f); + return NULL; + } + + spin_lock_init(&f->lock); + snd_use_lock_init(&f->use_lock); + init_waitqueue_head(&f->input_sleep); + atomic_set(&f->overflow, 0); + + f->head = NULL; + f->tail = NULL; + f->cells = 0; + + return f; +} + +void snd_seq_fifo_delete(fifo_t **fifo) +{ + fifo_t *f; + + snd_assert(fifo != NULL, return); + f = *fifo; + snd_assert(f != NULL, return); + *fifo = NULL; + + snd_seq_fifo_clear(f); + + /* wake up clients if any */ + if (waitqueue_active(&f->input_sleep)) + wake_up(&f->input_sleep); + + /* release resources...*/ + /*....................*/ + + if (f->pool) { + snd_seq_pool_done(f->pool); + snd_seq_pool_delete(&f->pool); + } + + kfree(f); +} + +static snd_seq_event_cell_t *fifo_cell_out(fifo_t *f); + +/* clear queue */ +void snd_seq_fifo_clear(fifo_t *f) +{ + snd_seq_event_cell_t *cell; + unsigned long flags; + + /* clear overflow flag */ + atomic_set(&f->overflow, 0); + + snd_use_lock_sync(&f->use_lock); + spin_lock_irqsave(&f->lock, flags); + /* drain the fifo */ + while ((cell = fifo_cell_out(f)) != NULL) { + snd_seq_cell_free(cell); + } + spin_unlock_irqrestore(&f->lock, flags); +} + + +/* enqueue event to fifo */ +int snd_seq_fifo_event_in(fifo_t *f, snd_seq_event_t *event) +{ + snd_seq_event_cell_t *cell; + unsigned long flags; + int err; + + snd_assert(f != NULL, return -EINVAL); + + snd_use_lock_use(&f->use_lock); + err = snd_seq_event_dup(f->pool, event, &cell, 1, NULL); /* always non-blocking */ + if (err < 0) { + if (err == -ENOMEM) + atomic_inc(&f->overflow); + snd_use_lock_free(&f->use_lock); + return err; + } + + /* append new cells to fifo */ + spin_lock_irqsave(&f->lock, flags); + if (f->tail != NULL) + f->tail->next = cell; + f->tail = cell; + if (f->head == NULL) + f->head = cell; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + + /* wakeup client */ + if (waitqueue_active(&f->input_sleep)) + wake_up(&f->input_sleep); + + snd_use_lock_free(&f->use_lock); + + return 0; /* success */ + +} + +/* dequeue cell from fifo */ +static snd_seq_event_cell_t *fifo_cell_out(fifo_t *f) +{ + snd_seq_event_cell_t *cell; + + if ((cell = f->head) != NULL) { + f->head = cell->next; + + /* reset tail if this was the last element */ + if (f->tail == cell) + f->tail = NULL; + + cell->next = NULL; + f->cells--; + } + + return cell; +} + +/* dequeue cell from fifo and copy on user space */ +int snd_seq_fifo_cell_out(fifo_t *f, snd_seq_event_cell_t **cellp, int nonblock) +{ + snd_seq_event_cell_t *cell; + unsigned long flags; + wait_queue_t wait; + + snd_assert(f != NULL, return -EINVAL); + + *cellp = NULL; + init_waitqueue_entry(&wait, current); + spin_lock_irqsave(&f->lock, flags); + while ((cell = fifo_cell_out(f)) == NULL) { + if (nonblock) { + /* non-blocking - return immediately */ + spin_unlock_irqrestore(&f->lock, flags); + return -EAGAIN; + } + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&f->input_sleep, &wait); + spin_unlock_irq(&f->lock); + schedule(); + spin_lock_irq(&f->lock); + remove_wait_queue(&f->input_sleep, &wait); + if (signal_pending(current)) { + spin_unlock_irqrestore(&f->lock, flags); + return -ERESTARTSYS; + } + } + spin_unlock_irqrestore(&f->lock, flags); + *cellp = cell; + + return 0; +} + + +void snd_seq_fifo_cell_putback(fifo_t *f, snd_seq_event_cell_t *cell) +{ + unsigned long flags; + + if (cell) { + spin_lock_irqsave(&f->lock, flags); + cell->next = f->head; + f->head = cell; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + } +} + + +/* polling; return non-zero if queue is available */ +int snd_seq_fifo_poll_wait(fifo_t *f, struct file *file, poll_table *wait) +{ + poll_wait(file, &f->input_sleep, wait); + return (f->cells > 0); +} + +/* change the size of pool; all old events are removed */ +int snd_seq_fifo_resize(fifo_t *f, int poolsize) +{ + unsigned long flags; + pool_t *newpool, *oldpool; + snd_seq_event_cell_t *cell, *next, *oldhead; + + snd_assert(f != NULL && f->pool != NULL, return -EINVAL); + + /* allocate new pool */ + newpool = snd_seq_pool_new(poolsize); + if (newpool == NULL) + return -ENOMEM; + if (snd_seq_pool_init(newpool) < 0) { + snd_seq_pool_delete(&newpool); + return -ENOMEM; + } + + spin_lock_irqsave(&f->lock, flags); + /* remember old pool */ + oldpool = f->pool; + oldhead = f->head; + /* exchange pools */ + f->pool = newpool; + f->head = NULL; + f->tail = NULL; + f->cells = 0; + /* NOTE: overflow flag is not cleared */ + spin_unlock_irqrestore(&f->lock, flags); + + /* release cells in old pool */ + for (cell = oldhead; cell; cell = next) { + next = cell->next; + snd_seq_cell_free(cell); + } + snd_seq_pool_delete(&oldpool); + + return 0; +} diff --git a/sound/core/seq/seq_fifo.h b/sound/core/seq/seq_fifo.h new file mode 100644 index 000000000000..d677c261b0a4 --- /dev/null +++ b/sound/core/seq/seq_fifo.h @@ -0,0 +1,72 @@ +/* + * ALSA sequencer FIFO + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_FIFO_H +#define __SND_SEQ_FIFO_H + +#include "seq_memory.h" +#include "seq_lock.h" + + +/* === FIFO === */ + +typedef struct { + pool_t *pool; /* FIFO pool */ + snd_seq_event_cell_t* head; /* pointer to head of fifo */ + snd_seq_event_cell_t* tail; /* pointer to tail of fifo */ + int cells; + spinlock_t lock; + snd_use_lock_t use_lock; + wait_queue_head_t input_sleep; + atomic_t overflow; + +} fifo_t; + +/* create new fifo (constructor) */ +extern fifo_t *snd_seq_fifo_new(int poolsize); + +/* delete fifo (destructor) */ +extern void snd_seq_fifo_delete(fifo_t **f); + + +/* enqueue event to fifo */ +extern int snd_seq_fifo_event_in(fifo_t *f, snd_seq_event_t *event); + +/* lock fifo from release */ +#define snd_seq_fifo_lock(fifo) snd_use_lock_use(&(fifo)->use_lock) +#define snd_seq_fifo_unlock(fifo) snd_use_lock_free(&(fifo)->use_lock) + +/* get a cell from fifo - fifo should be locked */ +int snd_seq_fifo_cell_out(fifo_t *f, snd_seq_event_cell_t **cellp, int nonblock); + +/* free dequeued cell - fifo should be locked */ +extern void snd_seq_fifo_cell_putback(fifo_t *f, snd_seq_event_cell_t *cell); + +/* clean up queue */ +extern void snd_seq_fifo_clear(fifo_t *f); + +/* polling */ +extern int snd_seq_fifo_poll_wait(fifo_t *f, struct file *file, poll_table *wait); + +/* resize pool in fifo */ +int snd_seq_fifo_resize(fifo_t *f, int poolsize); + + +#endif diff --git a/sound/core/seq/seq_info.c b/sound/core/seq/seq_info.c new file mode 100644 index 000000000000..b50b695c41c4 --- /dev/null +++ b/sound/core/seq/seq_info.c @@ -0,0 +1,75 @@ +/* + * ALSA sequencer /proc interface + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <sound/core.h> + +#include "seq_info.h" +#include "seq_clientmgr.h" +#include "seq_timer.h" + + +static snd_info_entry_t *queues_entry; +static snd_info_entry_t *clients_entry; +static snd_info_entry_t *timer_entry; + + +static snd_info_entry_t * __init +create_info_entry(char *name, int size, void (*read)(snd_info_entry_t *, snd_info_buffer_t *)) +{ + snd_info_entry_t *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, name, snd_seq_root); + if (entry == NULL) + return NULL; + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->c.text.read_size = size; + entry->c.text.read = read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return NULL; + } + return entry; +} + + +/* create all our /proc entries */ +int __init snd_seq_info_init(void) +{ + queues_entry = create_info_entry("queues", 512 + (256 * SNDRV_SEQ_MAX_QUEUES), + snd_seq_info_queues_read); + clients_entry = create_info_entry("clients", 512 + (256 * SNDRV_SEQ_MAX_CLIENTS), + snd_seq_info_clients_read); + timer_entry = create_info_entry("timer", 1024, snd_seq_info_timer_read); + return 0; +} + +int __exit snd_seq_info_done(void) +{ + if (queues_entry) + snd_info_unregister(queues_entry); + if (clients_entry) + snd_info_unregister(clients_entry); + if (timer_entry) + snd_info_unregister(timer_entry); + return 0; +} diff --git a/sound/core/seq/seq_info.h b/sound/core/seq/seq_info.h new file mode 100644 index 000000000000..efd099a858e4 --- /dev/null +++ b/sound/core/seq/seq_info.h @@ -0,0 +1,36 @@ +/* + * ALSA sequencer /proc info + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_INFO_H +#define __SND_SEQ_INFO_H + +#include <sound/info.h> +#include <sound/seq_kernel.h> + +void snd_seq_info_clients_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer); +void snd_seq_info_timer_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer); +void snd_seq_info_queues_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer); + + +int snd_seq_info_init( void ); +int snd_seq_info_done( void ); + + +#endif diff --git a/sound/core/seq/seq_instr.c b/sound/core/seq/seq_instr.c new file mode 100644 index 000000000000..5b40ea2ba8f4 --- /dev/null +++ b/sound/core/seq/seq_instr.c @@ -0,0 +1,653 @@ +/* + * Generic Instrument routines for ALSA sequencer + * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "seq_clientmgr.h" +#include <sound/seq_instr.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer instrument library."); +MODULE_LICENSE("GPL"); + + +static void snd_instr_lock_ops(snd_seq_kinstr_list_t *list) +{ + if (!(list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT)) { + spin_lock_irqsave(&list->ops_lock, list->ops_flags); + } else { + down(&list->ops_mutex); + } +} + +static void snd_instr_unlock_ops(snd_seq_kinstr_list_t *list) +{ + if (!(list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT)) { + spin_unlock_irqrestore(&list->ops_lock, list->ops_flags); + } else { + up(&list->ops_mutex); + } +} + +static snd_seq_kinstr_t *snd_seq_instr_new(int add_len, int atomic) +{ + snd_seq_kinstr_t *instr; + + instr = kcalloc(1, sizeof(snd_seq_kinstr_t) + add_len, atomic ? GFP_ATOMIC : GFP_KERNEL); + if (instr == NULL) + return NULL; + instr->add_len = add_len; + return instr; +} + +static int snd_seq_instr_free(snd_seq_kinstr_t *instr, int atomic) +{ + int result = 0; + + if (instr == NULL) + return -EINVAL; + if (instr->ops && instr->ops->remove) + result = instr->ops->remove(instr->ops->private_data, instr, 1); + if (!result) + kfree(instr); + return result; +} + +snd_seq_kinstr_list_t *snd_seq_instr_list_new(void) +{ + snd_seq_kinstr_list_t *list; + + list = kcalloc(1, sizeof(snd_seq_kinstr_list_t), GFP_KERNEL); + if (list == NULL) + return NULL; + spin_lock_init(&list->lock); + spin_lock_init(&list->ops_lock); + init_MUTEX(&list->ops_mutex); + list->owner = -1; + return list; +} + +void snd_seq_instr_list_free(snd_seq_kinstr_list_t **list_ptr) +{ + snd_seq_kinstr_list_t *list; + snd_seq_kinstr_t *instr; + snd_seq_kcluster_t *cluster; + int idx; + unsigned long flags; + + if (list_ptr == NULL) + return; + list = *list_ptr; + *list_ptr = NULL; + if (list == NULL) + return; + + for (idx = 0; idx < SNDRV_SEQ_INSTR_HASH_SIZE; idx++) { + while ((instr = list->hash[idx]) != NULL) { + list->hash[idx] = instr->next; + list->count--; + spin_lock_irqsave(&list->lock, flags); + while (instr->use) { + spin_unlock_irqrestore(&list->lock, flags); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + spin_lock_irqsave(&list->lock, flags); + } + spin_unlock_irqrestore(&list->lock, flags); + if (snd_seq_instr_free(instr, 0)<0) + snd_printk(KERN_WARNING "instrument free problem\n"); + } + while ((cluster = list->chash[idx]) != NULL) { + list->chash[idx] = cluster->next; + list->ccount--; + kfree(cluster); + } + } + kfree(list); +} + +static int instr_free_compare(snd_seq_kinstr_t *instr, + snd_seq_instr_header_t *ifree, + unsigned int client) +{ + switch (ifree->cmd) { + case SNDRV_SEQ_INSTR_FREE_CMD_ALL: + /* all, except private for other clients */ + if ((instr->instr.std & 0xff000000) == 0) + return 0; + if (((instr->instr.std >> 24) & 0xff) == client) + return 0; + return 1; + case SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE: + /* all my private instruments */ + if ((instr->instr.std & 0xff000000) == 0) + return 1; + if (((instr->instr.std >> 24) & 0xff) == client) + return 0; + return 1; + case SNDRV_SEQ_INSTR_FREE_CMD_CLUSTER: + /* all my private instruments */ + if ((instr->instr.std & 0xff000000) == 0) { + if (instr->instr.cluster == ifree->id.cluster) + return 0; + return 1; + } + if (((instr->instr.std >> 24) & 0xff) == client) { + if (instr->instr.cluster == ifree->id.cluster) + return 0; + } + return 1; + } + return 1; +} + +int snd_seq_instr_list_free_cond(snd_seq_kinstr_list_t *list, + snd_seq_instr_header_t *ifree, + int client, + int atomic) +{ + snd_seq_kinstr_t *instr, *prev, *next, *flist; + int idx; + unsigned long flags; + + snd_instr_lock_ops(list); + for (idx = 0; idx < SNDRV_SEQ_INSTR_HASH_SIZE; idx++) { + spin_lock_irqsave(&list->lock, flags); + instr = list->hash[idx]; + prev = flist = NULL; + while (instr) { + while (instr && instr_free_compare(instr, ifree, (unsigned int)client)) { + prev = instr; + instr = instr->next; + } + if (instr == NULL) + continue; + if (instr->ops && instr->ops->notify) + instr->ops->notify(instr->ops->private_data, instr, SNDRV_SEQ_INSTR_NOTIFY_REMOVE); + next = instr->next; + if (prev == NULL) { + list->hash[idx] = next; + } else { + prev->next = next; + } + list->count--; + instr->next = flist; + flist = instr; + instr = next; + } + spin_unlock_irqrestore(&list->lock, flags); + while (flist) { + instr = flist; + flist = instr->next; + while (instr->use) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + } + if (snd_seq_instr_free(instr, atomic)<0) + snd_printk(KERN_WARNING "instrument free problem\n"); + instr = next; + } + } + snd_instr_unlock_ops(list); + return 0; +} + +static int compute_hash_instr_key(snd_seq_instr_t *instr) +{ + int result; + + result = instr->bank | (instr->prg << 16); + result += result >> 24; + result += result >> 16; + result += result >> 8; + return result & (SNDRV_SEQ_INSTR_HASH_SIZE-1); +} + +#if 0 +static int compute_hash_cluster_key(snd_seq_instr_cluster_t cluster) +{ + int result; + + result = cluster; + result += result >> 24; + result += result >> 16; + result += result >> 8; + return result & (SNDRV_SEQ_INSTR_HASH_SIZE-1); +} +#endif + +static int compare_instr(snd_seq_instr_t *i1, snd_seq_instr_t *i2, int exact) +{ + if (exact) { + if (i1->cluster != i2->cluster || + i1->bank != i2->bank || + i1->prg != i2->prg) + return 1; + if ((i1->std & 0xff000000) != (i2->std & 0xff000000)) + return 1; + if (!(i1->std & i2->std)) + return 1; + return 0; + } else { + unsigned int client_check; + + if (i2->cluster && i1->cluster != i2->cluster) + return 1; + client_check = i2->std & 0xff000000; + if (client_check) { + if ((i1->std & 0xff000000) != client_check) + return 1; + } else { + if ((i1->std & i2->std) != i2->std) + return 1; + } + return i1->bank != i2->bank || i1->prg != i2->prg; + } +} + +snd_seq_kinstr_t *snd_seq_instr_find(snd_seq_kinstr_list_t *list, + snd_seq_instr_t *instr, + int exact, + int follow_alias) +{ + unsigned long flags; + int depth = 0; + snd_seq_kinstr_t *result; + + if (list == NULL || instr == NULL) + return NULL; + spin_lock_irqsave(&list->lock, flags); + __again: + result = list->hash[compute_hash_instr_key(instr)]; + while (result) { + if (!compare_instr(&result->instr, instr, exact)) { + if (follow_alias && (result->type == SNDRV_SEQ_INSTR_ATYPE_ALIAS)) { + instr = (snd_seq_instr_t *)KINSTR_DATA(result); + if (++depth > 10) + goto __not_found; + goto __again; + } + result->use++; + spin_unlock_irqrestore(&list->lock, flags); + return result; + } + result = result->next; + } + __not_found: + spin_unlock_irqrestore(&list->lock, flags); + return NULL; +} + +void snd_seq_instr_free_use(snd_seq_kinstr_list_t *list, + snd_seq_kinstr_t *instr) +{ + unsigned long flags; + + if (list == NULL || instr == NULL) + return; + spin_lock_irqsave(&list->lock, flags); + if (instr->use <= 0) { + snd_printk(KERN_ERR "free_use: fatal!!! use = %i, name = '%s'\n", instr->use, instr->name); + } else { + instr->use--; + } + spin_unlock_irqrestore(&list->lock, flags); +} + +static snd_seq_kinstr_ops_t *instr_ops(snd_seq_kinstr_ops_t *ops, char *instr_type) +{ + while (ops) { + if (!strcmp(ops->instr_type, instr_type)) + return ops; + ops = ops->next; + } + return NULL; +} + +static int instr_result(snd_seq_event_t *ev, + int type, int result, + int atomic) +{ + snd_seq_event_t sev; + + memset(&sev, 0, sizeof(sev)); + sev.type = SNDRV_SEQ_EVENT_RESULT; + sev.flags = SNDRV_SEQ_TIME_STAMP_REAL | SNDRV_SEQ_EVENT_LENGTH_FIXED | + SNDRV_SEQ_PRIORITY_NORMAL; + sev.source = ev->dest; + sev.dest = ev->source; + sev.data.result.event = type; + sev.data.result.result = result; +#if 0 + printk("instr result - type = %i, result = %i, queue = %i, source.client:port = %i:%i, dest.client:port = %i:%i\n", + type, result, + sev.queue, + sev.source.client, sev.source.port, + sev.dest.client, sev.dest.port); +#endif + return snd_seq_kernel_client_dispatch(sev.source.client, &sev, atomic, 0); +} + +static int instr_begin(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + unsigned long flags; + + spin_lock_irqsave(&list->lock, flags); + if (list->owner >= 0 && list->owner != ev->source.client) { + spin_unlock_irqrestore(&list->lock, flags); + return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_BEGIN, -EBUSY, atomic); + } + list->owner = ev->source.client; + spin_unlock_irqrestore(&list->lock, flags); + return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_BEGIN, 0, atomic); +} + +static int instr_end(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + unsigned long flags; + + /* TODO: timeout handling */ + spin_lock_irqsave(&list->lock, flags); + if (list->owner == ev->source.client) { + list->owner = -1; + spin_unlock_irqrestore(&list->lock, flags); + return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_END, 0, atomic); + } + spin_unlock_irqrestore(&list->lock, flags); + return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_END, -EINVAL, atomic); +} + +static int instr_info(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_format_info(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_reset(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_status(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_put(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + unsigned long flags; + snd_seq_instr_header_t put; + snd_seq_kinstr_t *instr; + int result = -EINVAL, len, key; + + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARUSR) + goto __return; + + if (ev->data.ext.len < sizeof(snd_seq_instr_header_t)) + goto __return; + if (copy_from_user(&put, (void __user *)ev->data.ext.ptr, sizeof(snd_seq_instr_header_t))) { + result = -EFAULT; + goto __return; + } + snd_instr_lock_ops(list); + if (put.id.instr.std & 0xff000000) { /* private instrument */ + put.id.instr.std &= 0x00ffffff; + put.id.instr.std |= (unsigned int)ev->source.client << 24; + } + if ((instr = snd_seq_instr_find(list, &put.id.instr, 1, 0))) { + snd_seq_instr_free_use(list, instr); + snd_instr_unlock_ops(list); + result = -EBUSY; + goto __return; + } + ops = instr_ops(ops, put.data.data.format); + if (ops == NULL) { + snd_instr_unlock_ops(list); + goto __return; + } + len = ops->add_len; + if (put.data.type == SNDRV_SEQ_INSTR_ATYPE_ALIAS) + len = sizeof(snd_seq_instr_t); + instr = snd_seq_instr_new(len, atomic); + if (instr == NULL) { + snd_instr_unlock_ops(list); + result = -ENOMEM; + goto __return; + } + instr->ops = ops; + instr->instr = put.id.instr; + strlcpy(instr->name, put.data.name, sizeof(instr->name)); + instr->type = put.data.type; + if (instr->type == SNDRV_SEQ_INSTR_ATYPE_DATA) { + result = ops->put(ops->private_data, + instr, + (void __user *)ev->data.ext.ptr + sizeof(snd_seq_instr_header_t), + ev->data.ext.len - sizeof(snd_seq_instr_header_t), + atomic, + put.cmd); + if (result < 0) { + snd_seq_instr_free(instr, atomic); + snd_instr_unlock_ops(list); + goto __return; + } + } + key = compute_hash_instr_key(&instr->instr); + spin_lock_irqsave(&list->lock, flags); + instr->next = list->hash[key]; + list->hash[key] = instr; + list->count++; + spin_unlock_irqrestore(&list->lock, flags); + snd_instr_unlock_ops(list); + result = 0; + __return: + instr_result(ev, SNDRV_SEQ_EVENT_INSTR_PUT, result, atomic); + return result; +} + +static int instr_get(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_free(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + snd_seq_instr_header_t ifree; + snd_seq_kinstr_t *instr, *prev; + int result = -EINVAL; + unsigned long flags; + unsigned int hash; + + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARUSR) + goto __return; + + if (ev->data.ext.len < sizeof(snd_seq_instr_header_t)) + goto __return; + if (copy_from_user(&ifree, (void __user *)ev->data.ext.ptr, sizeof(snd_seq_instr_header_t))) { + result = -EFAULT; + goto __return; + } + if (ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_ALL || + ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE || + ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_CLUSTER) { + result = snd_seq_instr_list_free_cond(list, &ifree, ev->dest.client, atomic); + goto __return; + } + if (ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_SINGLE) { + if (ifree.id.instr.std & 0xff000000) { + ifree.id.instr.std &= 0x00ffffff; + ifree.id.instr.std |= (unsigned int)ev->source.client << 24; + } + hash = compute_hash_instr_key(&ifree.id.instr); + snd_instr_lock_ops(list); + spin_lock_irqsave(&list->lock, flags); + instr = list->hash[hash]; + prev = NULL; + while (instr) { + if (!compare_instr(&instr->instr, &ifree.id.instr, 1)) + goto __free_single; + prev = instr; + instr = instr->next; + } + result = -ENOENT; + spin_unlock_irqrestore(&list->lock, flags); + snd_instr_unlock_ops(list); + goto __return; + + __free_single: + if (prev) { + prev->next = instr->next; + } else { + list->hash[hash] = instr->next; + } + if (instr->ops && instr->ops->notify) + instr->ops->notify(instr->ops->private_data, instr, SNDRV_SEQ_INSTR_NOTIFY_REMOVE); + while (instr->use) { + spin_unlock_irqrestore(&list->lock, flags); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + spin_lock_irqsave(&list->lock, flags); + } + spin_unlock_irqrestore(&list->lock, flags); + result = snd_seq_instr_free(instr, atomic); + snd_instr_unlock_ops(list); + goto __return; + } + + __return: + instr_result(ev, SNDRV_SEQ_EVENT_INSTR_FREE, result, atomic); + return result; +} + +static int instr_list(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_cluster(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +int snd_seq_instr_event(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int client, + int atomic, + int hop) +{ + int direct = 0; + + snd_assert(ops != NULL && list != NULL && ev != NULL, return -EINVAL); + if (snd_seq_ev_is_direct(ev)) { + direct = 1; + switch (ev->type) { + case SNDRV_SEQ_EVENT_INSTR_BEGIN: + return instr_begin(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_END: + return instr_end(ops, list, ev, atomic, hop); + } + } + if ((list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT) && !direct) + return -EINVAL; + switch (ev->type) { + case SNDRV_SEQ_EVENT_INSTR_INFO: + return instr_info(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_FINFO: + return instr_format_info(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_RESET: + return instr_reset(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_STATUS: + return instr_status(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_PUT: + return instr_put(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_GET: + return instr_get(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_FREE: + return instr_free(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_LIST: + return instr_list(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_CLUSTER: + return instr_cluster(ops, list, ev, atomic, hop); + } + return -EINVAL; +} + +/* + * Init part + */ + +static int __init alsa_seq_instr_init(void) +{ + return 0; +} + +static void __exit alsa_seq_instr_exit(void) +{ +} + +module_init(alsa_seq_instr_init) +module_exit(alsa_seq_instr_exit) + +EXPORT_SYMBOL(snd_seq_instr_list_new); +EXPORT_SYMBOL(snd_seq_instr_list_free); +EXPORT_SYMBOL(snd_seq_instr_list_free_cond); +EXPORT_SYMBOL(snd_seq_instr_find); +EXPORT_SYMBOL(snd_seq_instr_free_use); +EXPORT_SYMBOL(snd_seq_instr_event); diff --git a/sound/core/seq/seq_lock.c b/sound/core/seq/seq_lock.c new file mode 100644 index 000000000000..b09cee058fa7 --- /dev/null +++ b/sound/core/seq/seq_lock.c @@ -0,0 +1,48 @@ +/* + * Do sleep inside a spin-lock + * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <sound/core.h> +#include "seq_lock.h" + +#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG) + +/* wait until all locks are released */ +void snd_use_lock_sync_helper(snd_use_lock_t *lockp, const char *file, int line) +{ + int max_count = 5 * HZ; + + if (atomic_read(lockp) < 0) { + printk(KERN_WARNING "seq_lock: lock trouble [counter = %d] in %s:%d\n", atomic_read(lockp), file, line); + return; + } + while (atomic_read(lockp) > 0) { + if (max_count == 0) { + snd_printk(KERN_WARNING "seq_lock: timeout [%d left] in %s:%d\n", atomic_read(lockp), file, line); + break; + } + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + max_count--; + } +} + +#endif diff --git a/sound/core/seq/seq_lock.h b/sound/core/seq/seq_lock.h new file mode 100644 index 000000000000..54044bc2c9ef --- /dev/null +++ b/sound/core/seq/seq_lock.h @@ -0,0 +1,33 @@ +#ifndef __SND_SEQ_LOCK_H +#define __SND_SEQ_LOCK_H + +#include <linux/sched.h> + +#if defined(CONFIG_SMP) || defined(CONFIG_SND_DEBUG) + +typedef atomic_t snd_use_lock_t; + +/* initialize lock */ +#define snd_use_lock_init(lockp) atomic_set(lockp, 0) + +/* increment lock */ +#define snd_use_lock_use(lockp) atomic_inc(lockp) + +/* release lock */ +#define snd_use_lock_free(lockp) atomic_dec(lockp) + +/* wait until all locks are released */ +void snd_use_lock_sync_helper(snd_use_lock_t *lock, const char *file, int line); +#define snd_use_lock_sync(lockp) snd_use_lock_sync_helper(lockp, __BASE_FILE__, __LINE__) + +#else /* SMP || CONFIG_SND_DEBUG */ + +typedef spinlock_t snd_use_lock_t; /* dummy */ +#define snd_use_lock_init(lockp) /**/ +#define snd_use_lock_use(lockp) /**/ +#define snd_use_lock_free(lockp) /**/ +#define snd_use_lock_sync(lockp) /**/ + +#endif /* SMP || CONFIG_SND_DEBUG */ + +#endif /* __SND_SEQ_LOCK_H */ diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c new file mode 100644 index 000000000000..00d841e82fbc --- /dev/null +++ b/sound/core/seq/seq_memory.c @@ -0,0 +1,510 @@ +/* + * ALSA sequencer Memory Manager + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@suse.cz> + * 2000 by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <sound/core.h> + +#include <sound/seq_kernel.h> +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_info.h" +#include "seq_lock.h" + +/* semaphore in struct file record */ +#define semaphore_of(fp) ((fp)->f_dentry->d_inode->i_sem) + + +inline static int snd_seq_pool_available(pool_t *pool) +{ + return pool->total_elements - atomic_read(&pool->counter); +} + +inline static int snd_seq_output_ok(pool_t *pool) +{ + return snd_seq_pool_available(pool) >= pool->room; +} + +/* + * Variable length event: + * The event like sysex uses variable length type. + * The external data may be stored in three different formats. + * 1) kernel space + * This is the normal case. + * ext.data.len = length + * ext.data.ptr = buffer pointer + * 2) user space + * When an event is generated via read(), the external data is + * kept in user space until expanded. + * ext.data.len = length | SNDRV_SEQ_EXT_USRPTR + * ext.data.ptr = userspace pointer + * 3) chained cells + * When the variable length event is enqueued (in prioq or fifo), + * the external data is decomposed to several cells. + * ext.data.len = length | SNDRV_SEQ_EXT_CHAINED + * ext.data.ptr = the additiona cell head + * -> cell.next -> cell.next -> .. + */ + +/* + * exported: + * call dump function to expand external data. + */ + +static int get_var_len(const snd_seq_event_t *event) +{ + if ((event->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + return -EINVAL; + + return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; +} + +int snd_seq_dump_var_event(const snd_seq_event_t *event, snd_seq_dump_func_t func, void *private_data) +{ + int len, err; + snd_seq_event_cell_t *cell; + + if ((len = get_var_len(event)) <= 0) + return len; + + if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { + char buf[32]; + char __user *curptr = (char __user *)event->data.ext.ptr; + while (len > 0) { + int size = sizeof(buf); + if (len < size) + size = len; + if (copy_from_user(buf, curptr, size)) + return -EFAULT; + err = func(private_data, buf, size); + if (err < 0) + return err; + curptr += size; + len -= size; + } + return 0; + } if (! (event->data.ext.len & SNDRV_SEQ_EXT_CHAINED)) { + return func(private_data, event->data.ext.ptr, len); + } + + cell = (snd_seq_event_cell_t*)event->data.ext.ptr; + for (; len > 0 && cell; cell = cell->next) { + int size = sizeof(snd_seq_event_t); + if (len < size) + size = len; + err = func(private_data, &cell->event, size); + if (err < 0) + return err; + len -= size; + } + return 0; +} + + +/* + * exported: + * expand the variable length event to linear buffer space. + */ + +static int seq_copy_in_kernel(char **bufptr, const void *src, int size) +{ + memcpy(*bufptr, src, size); + *bufptr += size; + return 0; +} + +static int seq_copy_in_user(char __user **bufptr, const void *src, int size) +{ + if (copy_to_user(*bufptr, src, size)) + return -EFAULT; + *bufptr += size; + return 0; +} + +int snd_seq_expand_var_event(const snd_seq_event_t *event, int count, char *buf, int in_kernel, int size_aligned) +{ + int len, newlen; + int err; + + if ((len = get_var_len(event)) < 0) + return len; + newlen = len; + if (size_aligned > 0) + newlen = ((len + size_aligned - 1) / size_aligned) * size_aligned; + if (count < newlen) + return -EAGAIN; + + if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { + if (! in_kernel) + return -EINVAL; + if (copy_from_user(buf, (void __user *)event->data.ext.ptr, len)) + return -EFAULT; + return newlen; + } + err = snd_seq_dump_var_event(event, + in_kernel ? (snd_seq_dump_func_t)seq_copy_in_kernel : + (snd_seq_dump_func_t)seq_copy_in_user, + &buf); + return err < 0 ? err : newlen; +} + + +/* + * release this cell, free extended data if available + */ + +static inline void free_cell(pool_t *pool, snd_seq_event_cell_t *cell) +{ + cell->next = pool->free; + pool->free = cell; + atomic_dec(&pool->counter); +} + +void snd_seq_cell_free(snd_seq_event_cell_t * cell) +{ + unsigned long flags; + pool_t *pool; + + snd_assert(cell != NULL, return); + pool = cell->pool; + snd_assert(pool != NULL, return); + + spin_lock_irqsave(&pool->lock, flags); + free_cell(pool, cell); + if (snd_seq_ev_is_variable(&cell->event)) { + if (cell->event.data.ext.len & SNDRV_SEQ_EXT_CHAINED) { + snd_seq_event_cell_t *curp, *nextptr; + curp = cell->event.data.ext.ptr; + for (; curp; curp = nextptr) { + nextptr = curp->next; + curp->next = pool->free; + free_cell(pool, curp); + } + } + } + if (waitqueue_active(&pool->output_sleep)) { + /* has enough space now? */ + if (snd_seq_output_ok(pool)) + wake_up(&pool->output_sleep); + } + spin_unlock_irqrestore(&pool->lock, flags); +} + + +/* + * allocate an event cell. + */ +static int snd_seq_cell_alloc(pool_t *pool, snd_seq_event_cell_t **cellp, int nonblock, struct file *file) +{ + snd_seq_event_cell_t *cell; + unsigned long flags; + int err = -EAGAIN; + wait_queue_t wait; + + if (pool == NULL) + return -EINVAL; + + *cellp = NULL; + + init_waitqueue_entry(&wait, current); + spin_lock_irqsave(&pool->lock, flags); + if (pool->ptr == NULL) { /* not initialized */ + snd_printd("seq: pool is not initialized\n"); + err = -EINVAL; + goto __error; + } + while (pool->free == NULL && ! nonblock && ! pool->closing) { + + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&pool->output_sleep, &wait); + spin_unlock_irq(&pool->lock); + schedule(); + spin_lock_irq(&pool->lock); + remove_wait_queue(&pool->output_sleep, &wait); + /* interrupted? */ + if (signal_pending(current)) { + err = -ERESTARTSYS; + goto __error; + } + } + if (pool->closing) { /* closing.. */ + err = -ENOMEM; + goto __error; + } + + cell = pool->free; + if (cell) { + int used; + pool->free = cell->next; + atomic_inc(&pool->counter); + used = atomic_read(&pool->counter); + if (pool->max_used < used) + pool->max_used = used; + pool->event_alloc_success++; + /* clear cell pointers */ + cell->next = NULL; + err = 0; + } else + pool->event_alloc_failures++; + *cellp = cell; + +__error: + spin_unlock_irqrestore(&pool->lock, flags); + return err; +} + + +/* + * duplicate the event to a cell. + * if the event has external data, the data is decomposed to additional + * cells. + */ +int snd_seq_event_dup(pool_t *pool, snd_seq_event_t *event, snd_seq_event_cell_t **cellp, int nonblock, struct file *file) +{ + int ncells, err; + unsigned int extlen; + snd_seq_event_cell_t *cell; + + *cellp = NULL; + + ncells = 0; + extlen = 0; + if (snd_seq_ev_is_variable(event)) { + extlen = event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; + ncells = (extlen + sizeof(snd_seq_event_t) - 1) / sizeof(snd_seq_event_t); + } + if (ncells >= pool->total_elements) + return -ENOMEM; + + err = snd_seq_cell_alloc(pool, &cell, nonblock, file); + if (err < 0) + return err; + + /* copy the event */ + cell->event = *event; + + /* decompose */ + if (snd_seq_ev_is_variable(event)) { + int len = extlen; + int is_chained = event->data.ext.len & SNDRV_SEQ_EXT_CHAINED; + int is_usrptr = event->data.ext.len & SNDRV_SEQ_EXT_USRPTR; + snd_seq_event_cell_t *src, *tmp, *tail; + char *buf; + + cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED; + cell->event.data.ext.ptr = NULL; + + src = (snd_seq_event_cell_t*)event->data.ext.ptr; + buf = (char *)event->data.ext.ptr; + tail = NULL; + + while (ncells-- > 0) { + int size = sizeof(snd_seq_event_t); + if (len < size) + size = len; + err = snd_seq_cell_alloc(pool, &tmp, nonblock, file); + if (err < 0) + goto __error; + if (cell->event.data.ext.ptr == NULL) + cell->event.data.ext.ptr = tmp; + if (tail) + tail->next = tmp; + tail = tmp; + /* copy chunk */ + if (is_chained && src) { + tmp->event = src->event; + src = src->next; + } else if (is_usrptr) { + if (copy_from_user(&tmp->event, (char __user *)buf, size)) { + err = -EFAULT; + goto __error; + } + } else { + memcpy(&tmp->event, buf, size); + } + buf += size; + len -= size; + } + } + + *cellp = cell; + return 0; + +__error: + snd_seq_cell_free(cell); + return err; +} + + +/* poll wait */ +int snd_seq_pool_poll_wait(pool_t *pool, struct file *file, poll_table *wait) +{ + poll_wait(file, &pool->output_sleep, wait); + return snd_seq_output_ok(pool); +} + + +/* allocate room specified number of events */ +int snd_seq_pool_init(pool_t *pool) +{ + int cell; + snd_seq_event_cell_t *cellptr; + unsigned long flags; + + snd_assert(pool != NULL, return -EINVAL); + if (pool->ptr) /* should be atomic? */ + return 0; + + pool->ptr = vmalloc(sizeof(snd_seq_event_cell_t) * pool->size); + if (pool->ptr == NULL) { + snd_printd("seq: malloc for sequencer events failed\n"); + return -ENOMEM; + } + + /* add new cells to the free cell list */ + spin_lock_irqsave(&pool->lock, flags); + pool->free = NULL; + + for (cell = 0; cell < pool->size; cell++) { + cellptr = pool->ptr + cell; + cellptr->pool = pool; + cellptr->next = pool->free; + pool->free = cellptr; + } + pool->room = (pool->size + 1) / 2; + + /* init statistics */ + pool->max_used = 0; + pool->total_elements = pool->size; + spin_unlock_irqrestore(&pool->lock, flags); + return 0; +} + +/* remove events */ +int snd_seq_pool_done(pool_t *pool) +{ + unsigned long flags; + snd_seq_event_cell_t *ptr; + int max_count = 5 * HZ; + + snd_assert(pool != NULL, return -EINVAL); + + /* wait for closing all threads */ + spin_lock_irqsave(&pool->lock, flags); + pool->closing = 1; + spin_unlock_irqrestore(&pool->lock, flags); + + if (waitqueue_active(&pool->output_sleep)) + wake_up(&pool->output_sleep); + + while (atomic_read(&pool->counter) > 0) { + if (max_count == 0) { + snd_printk(KERN_WARNING "snd_seq_pool_done timeout: %d cells remain\n", atomic_read(&pool->counter)); + break; + } + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + max_count--; + } + + /* release all resources */ + spin_lock_irqsave(&pool->lock, flags); + ptr = pool->ptr; + pool->ptr = NULL; + pool->free = NULL; + pool->total_elements = 0; + spin_unlock_irqrestore(&pool->lock, flags); + + vfree(ptr); + + spin_lock_irqsave(&pool->lock, flags); + pool->closing = 0; + spin_unlock_irqrestore(&pool->lock, flags); + + return 0; +} + + +/* init new memory pool */ +pool_t *snd_seq_pool_new(int poolsize) +{ + pool_t *pool; + + /* create pool block */ + pool = kcalloc(1, sizeof(*pool), GFP_KERNEL); + if (pool == NULL) { + snd_printd("seq: malloc failed for pool\n"); + return NULL; + } + spin_lock_init(&pool->lock); + pool->ptr = NULL; + pool->free = NULL; + pool->total_elements = 0; + atomic_set(&pool->counter, 0); + pool->closing = 0; + init_waitqueue_head(&pool->output_sleep); + + pool->size = poolsize; + + /* init statistics */ + pool->max_used = 0; + return pool; +} + +/* remove memory pool */ +int snd_seq_pool_delete(pool_t **ppool) +{ + pool_t *pool = *ppool; + + *ppool = NULL; + if (pool == NULL) + return 0; + snd_seq_pool_done(pool); + kfree(pool); + return 0; +} + +/* initialize sequencer memory */ +int __init snd_sequencer_memory_init(void) +{ + return 0; +} + +/* release sequencer memory */ +void __exit snd_sequencer_memory_done(void) +{ +} + + +/* exported to seq_clientmgr.c */ +void snd_seq_info_pool(snd_info_buffer_t * buffer, pool_t *pool, char *space) +{ + if (pool == NULL) + return; + snd_iprintf(buffer, "%sPool size : %d\n", space, pool->total_elements); + snd_iprintf(buffer, "%sCells in use : %d\n", space, atomic_read(&pool->counter)); + snd_iprintf(buffer, "%sPeak cells in use : %d\n", space, pool->max_used); + snd_iprintf(buffer, "%sAlloc success : %d\n", space, pool->event_alloc_success); + snd_iprintf(buffer, "%sAlloc failures : %d\n", space, pool->event_alloc_failures); +} diff --git a/sound/core/seq/seq_memory.h b/sound/core/seq/seq_memory.h new file mode 100644 index 000000000000..6c4dde5d3d6f --- /dev/null +++ b/sound/core/seq/seq_memory.h @@ -0,0 +1,104 @@ +/* + * ALSA sequencer Memory Manager + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_MEMORYMGR_H +#define __SND_SEQ_MEMORYMGR_H + +#include <sound/seq_kernel.h> +#include <linux/poll.h> + +typedef struct pool pool_t; + +/* container for sequencer event (internal use) */ +typedef struct snd_seq_event_cell_t { + snd_seq_event_t event; + pool_t *pool; /* used pool */ + struct snd_seq_event_cell_t *next; /* next cell */ +} snd_seq_event_cell_t; + +/* design note: the pool is a contigious block of memory, if we dynamicly + want to add additional cells to the pool be better store this in another + pool as we need to know the base address of the pool when releasing + memory. */ + +struct pool { + snd_seq_event_cell_t *ptr; /* pointer to first event chunk */ + snd_seq_event_cell_t *free; /* pointer to the head of the free list */ + + int total_elements; /* pool size actually allocated */ + atomic_t counter; /* cells free */ + + int size; /* pool size to be allocated */ + int room; /* watermark for sleep/wakeup */ + + int closing; + + /* statistics */ + int max_used; + int event_alloc_nopool; + int event_alloc_failures; + int event_alloc_success; + + /* Write locking */ + wait_queue_head_t output_sleep; + + /* Pool lock */ + spinlock_t lock; +}; + +extern void snd_seq_cell_free(snd_seq_event_cell_t* cell); + +int snd_seq_event_dup(pool_t *pool, snd_seq_event_t *event, snd_seq_event_cell_t **cellp, int nonblock, struct file *file); + +/* return number of unused (free) cells */ +static inline int snd_seq_unused_cells(pool_t *pool) +{ + return pool ? pool->total_elements - atomic_read(&pool->counter) : 0; +} + +/* return total number of allocated cells */ +static inline int snd_seq_total_cells(pool_t *pool) +{ + return pool ? pool->total_elements : 0; +} + +/* init pool - allocate events */ +int snd_seq_pool_init(pool_t *pool); + +/* done pool - free events */ +int snd_seq_pool_done(pool_t *pool); + +/* create pool */ +pool_t *snd_seq_pool_new(int poolsize); + +/* remove pool */ +int snd_seq_pool_delete(pool_t **pool); + +/* init memory */ +int snd_sequencer_memory_init(void); + +/* release event memory */ +void snd_sequencer_memory_done(void); + +/* polling */ +int snd_seq_pool_poll_wait(pool_t *pool, struct file *file, poll_table *wait); + + +#endif diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c new file mode 100644 index 000000000000..18247db45db6 --- /dev/null +++ b/sound/core/seq/seq_midi.c @@ -0,0 +1,489 @@ +/* + * Generic MIDI synth driver for ALSA sequencer + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@suse.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* +Possible options for midisynth module: + - automatic opening of midi ports on first received event or subscription + (close will be performed when client leaves) +*/ + + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/moduleparam.h> +#include <asm/semaphore.h> +#include <sound/core.h> +#include <sound/rawmidi.h> +#include <sound/seq_kernel.h> +#include <sound/seq_device.h> +#include <sound/seq_midi_event.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI synth."); +MODULE_LICENSE("GPL"); +static int output_buffer_size = PAGE_SIZE; +module_param(output_buffer_size, int, 0644); +MODULE_PARM_DESC(output_buffer_size, "Output buffer size in bytes."); +static int input_buffer_size = PAGE_SIZE; +module_param(input_buffer_size, int, 0644); +MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes."); + +/* data for this midi synth driver */ +typedef struct { + snd_card_t *card; + int device; + int subdevice; + snd_rawmidi_file_t input_rfile; + snd_rawmidi_file_t output_rfile; + int seq_client; + int seq_port; + snd_midi_event_t *parser; +} seq_midisynth_t; + +typedef struct { + int seq_client; + int num_ports; + int ports_per_device[SNDRV_RAWMIDI_DEVICES]; + seq_midisynth_t *ports[SNDRV_RAWMIDI_DEVICES]; +} seq_midisynth_client_t; + +static seq_midisynth_client_t *synths[SNDRV_CARDS]; +static DECLARE_MUTEX(register_mutex); + +/* handle rawmidi input event (MIDI v1.0 stream) */ +static void snd_midi_input_event(snd_rawmidi_substream_t * substream) +{ + snd_rawmidi_runtime_t *runtime; + seq_midisynth_t *msynth; + snd_seq_event_t ev; + char buf[16], *pbuf; + long res, count; + + if (substream == NULL) + return; + runtime = substream->runtime; + msynth = (seq_midisynth_t *) runtime->private_data; + if (msynth == NULL) + return; + memset(&ev, 0, sizeof(ev)); + while (runtime->avail > 0) { + res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf)); + if (res <= 0) + continue; + if (msynth->parser == NULL) + continue; + pbuf = buf; + while (res > 0) { + count = snd_midi_event_encode(msynth->parser, pbuf, res, &ev); + if (count < 0) + break; + pbuf += count; + res -= count; + if (ev.type != SNDRV_SEQ_EVENT_NONE) { + ev.source.port = msynth->seq_port; + ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0); + /* clear event and reset header */ + memset(&ev, 0, sizeof(ev)); + } + } + } +} + +static int dump_midi(snd_rawmidi_substream_t *substream, const char *buf, int count) +{ + snd_rawmidi_runtime_t *runtime; + int tmp; + + snd_assert(substream != NULL || buf != NULL, return -EINVAL); + runtime = substream->runtime; + if ((tmp = runtime->avail) < count) { + snd_printd("warning, output event was lost (count = %i, available = %i)\n", count, tmp); + return -ENOMEM; + } + if (snd_rawmidi_kernel_write(substream, buf, count) < count) + return -EINVAL; + return 0; +} + +static int event_process_midi(snd_seq_event_t * ev, int direct, + void *private_data, int atomic, int hop) +{ + seq_midisynth_t *msynth = (seq_midisynth_t *) private_data; + unsigned char msg[10]; /* buffer for constructing midi messages */ + snd_rawmidi_substream_t *substream; + int res; + + snd_assert(msynth != NULL, return -EINVAL); + substream = msynth->output_rfile.output; + if (substream == NULL) + return -ENODEV; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { /* special case, to save space */ + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) { + /* invalid event */ + snd_printd("seq_midi: invalid sysex event flags = 0x%x\n", ev->flags); + return 0; + } + res = snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)dump_midi, substream); + snd_midi_event_reset_decode(msynth->parser); + if (res < 0) + return res; + } else { + if (msynth->parser == NULL) + return -EIO; + res = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev); + if (res < 0) + return res; + if ((res = dump_midi(substream, msg, res)) < 0) { + snd_midi_event_reset_decode(msynth->parser); + return res; + } + } + return 0; +} + + +static int snd_seq_midisynth_new(seq_midisynth_t *msynth, + snd_card_t *card, + int device, + int subdevice) +{ + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &msynth->parser) < 0) + return -ENOMEM; + msynth->card = card; + msynth->device = device; + msynth->subdevice = subdevice; + return 0; +} + +/* open associated midi device for input */ +static int midisynth_subscribe(void *private_data, snd_seq_port_subscribe_t *info) +{ + int err; + seq_midisynth_t *msynth = (seq_midisynth_t *)private_data; + snd_rawmidi_runtime_t *runtime; + snd_rawmidi_params_t params; + + /* open midi port */ + if ((err = snd_rawmidi_kernel_open(msynth->card->number, msynth->device, msynth->subdevice, SNDRV_RAWMIDI_LFLG_INPUT, &msynth->input_rfile)) < 0) { + snd_printd("midi input open failed!!!\n"); + return err; + } + runtime = msynth->input_rfile.input->runtime; + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = input_buffer_size; + if ((err = snd_rawmidi_input_params(msynth->input_rfile.input, ¶ms)) < 0) { + snd_rawmidi_kernel_release(&msynth->input_rfile); + return err; + } + snd_midi_event_reset_encode(msynth->parser); + runtime->event = snd_midi_input_event; + runtime->private_data = msynth; + snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0); + return 0; +} + +/* close associated midi device for input */ +static int midisynth_unsubscribe(void *private_data, snd_seq_port_subscribe_t *info) +{ + int err; + seq_midisynth_t *msynth = (seq_midisynth_t *)private_data; + + snd_assert(msynth->input_rfile.input != NULL, return -EINVAL); + err = snd_rawmidi_kernel_release(&msynth->input_rfile); + return err; +} + +/* open associated midi device for output */ +static int midisynth_use(void *private_data, snd_seq_port_subscribe_t *info) +{ + int err; + seq_midisynth_t *msynth = (seq_midisynth_t *)private_data; + snd_rawmidi_params_t params; + + /* open midi port */ + if ((err = snd_rawmidi_kernel_open(msynth->card->number, msynth->device, msynth->subdevice, SNDRV_RAWMIDI_LFLG_OUTPUT, &msynth->output_rfile)) < 0) { + snd_printd("midi output open failed!!!\n"); + return err; + } + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = output_buffer_size; + if ((err = snd_rawmidi_output_params(msynth->output_rfile.output, ¶ms)) < 0) { + snd_rawmidi_kernel_release(&msynth->output_rfile); + return err; + } + snd_midi_event_reset_decode(msynth->parser); + return 0; +} + +/* close associated midi device for output */ +static int midisynth_unuse(void *private_data, snd_seq_port_subscribe_t *info) +{ + seq_midisynth_t *msynth = (seq_midisynth_t *)private_data; + unsigned char buf = 0xff; /* MIDI reset */ + + snd_assert(msynth->output_rfile.output != NULL, return -EINVAL); + /* sending single MIDI reset message to shut the device up */ + snd_rawmidi_kernel_write(msynth->output_rfile.output, &buf, 1); + snd_rawmidi_drain_output(msynth->output_rfile.output); + return snd_rawmidi_kernel_release(&msynth->output_rfile); +} + +/* delete given midi synth port */ +static void snd_seq_midisynth_delete(seq_midisynth_t *msynth) +{ + if (msynth == NULL) + return; + + if (msynth->seq_client > 0) { + /* delete port */ + snd_seq_event_port_detach(msynth->seq_client, msynth->seq_port); + } + + if (msynth->parser) + snd_midi_event_free(msynth->parser); +} + +/* set our client name */ +static int set_client_name(seq_midisynth_client_t *client, snd_card_t *card, + snd_rawmidi_info_t *rmidi) +{ + snd_seq_client_info_t cinfo; + const char *name; + + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.client = client->seq_client; + cinfo.type = KERNEL_CLIENT; + name = rmidi->name[0] ? (const char *)rmidi->name : "External MIDI"; + strlcpy(cinfo.name, name, sizeof(cinfo.name)); + return snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo); +} + +/* register new midi synth port */ +static int +snd_seq_midisynth_register_port(snd_seq_device_t *dev) +{ + seq_midisynth_client_t *client; + seq_midisynth_t *msynth, *ms; + snd_seq_port_info_t *port; + snd_rawmidi_info_t *info; + int newclient = 0; + unsigned int p, ports; + snd_seq_client_callback_t callbacks; + snd_seq_port_callback_t pcallbacks; + snd_card_t *card = dev->card; + int device = dev->device; + unsigned int input_count = 0, output_count = 0; + + snd_assert(card != NULL && device >= 0 && device < SNDRV_RAWMIDI_DEVICES, return -EINVAL); + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + info->device = device; + info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + info->subdevice = 0; + if (snd_rawmidi_info_select(card, info) >= 0) + output_count = info->subdevices_count; + info->stream = SNDRV_RAWMIDI_STREAM_INPUT; + if (snd_rawmidi_info_select(card, info) >= 0) { + input_count = info->subdevices_count; + } + ports = output_count; + if (ports < input_count) + ports = input_count; + if (ports == 0) { + kfree(info); + return -ENODEV; + } + if (ports > (256 / SNDRV_RAWMIDI_DEVICES)) + ports = 256 / SNDRV_RAWMIDI_DEVICES; + + down(®ister_mutex); + client = synths[card->number]; + if (client == NULL) { + newclient = 1; + client = kcalloc(1, sizeof(*client), GFP_KERNEL); + if (client == NULL) { + up(®ister_mutex); + kfree(info); + return -ENOMEM; + } + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.private_data = client; + callbacks.allow_input = callbacks.allow_output = 1; + client->seq_client = snd_seq_create_kernel_client(card, 0, &callbacks); + if (client->seq_client < 0) { + kfree(client); + up(®ister_mutex); + kfree(info); + return -ENOMEM; + } + set_client_name(client, card, info); + } else if (device == 0) + set_client_name(client, card, info); /* use the first device's name */ + + msynth = kcalloc(ports, sizeof(seq_midisynth_t), GFP_KERNEL); + port = kmalloc(sizeof(*port), GFP_KERNEL); + if (msynth == NULL || port == NULL) + goto __nomem; + + for (p = 0; p < ports; p++) { + ms = &msynth[p]; + + if (snd_seq_midisynth_new(ms, card, device, p) < 0) + goto __nomem; + + /* declare port */ + memset(port, 0, sizeof(*port)); + port->addr.client = client->seq_client; + port->addr.port = device * (256 / SNDRV_RAWMIDI_DEVICES) + p; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + memset(info, 0, sizeof(*info)); + info->device = device; + if (p < output_count) + info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + else + info->stream = SNDRV_RAWMIDI_STREAM_INPUT; + info->subdevice = p; + if (snd_rawmidi_info_select(card, info) >= 0) + strcpy(port->name, info->subname); + if (! port->name[0]) { + if (info->name[0]) { + if (ports > 1) + snprintf(port->name, sizeof(port->name), "%s-%d", info->name, p); + else + snprintf(port->name, sizeof(port->name), "%s", info->name); + } else { + /* last resort */ + if (ports > 1) + sprintf(port->name, "MIDI %d-%d-%d", card->number, device, p); + else + sprintf(port->name, "MIDI %d-%d", card->number, device); + } + } + if ((info->flags & SNDRV_RAWMIDI_INFO_OUTPUT) && p < output_count) + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if ((info->flags & SNDRV_RAWMIDI_INFO_INPUT) && p < input_count) + port->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) && + info->flags & SNDRV_RAWMIDI_INFO_DUPLEX) + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC; + port->midi_channels = 16; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = ms; + pcallbacks.subscribe = midisynth_subscribe; + pcallbacks.unsubscribe = midisynth_unsubscribe; + pcallbacks.use = midisynth_use; + pcallbacks.unuse = midisynth_unuse; + pcallbacks.event_input = event_process_midi; + port->kernel = &pcallbacks; + if (snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_CREATE_PORT, port)<0) + goto __nomem; + ms->seq_client = client->seq_client; + ms->seq_port = port->addr.port; + } + client->ports_per_device[device] = ports; + client->ports[device] = msynth; + client->num_ports++; + if (newclient) + synths[card->number] = client; + up(®ister_mutex); + return 0; /* success */ + + __nomem: + if (msynth != NULL) { + for (p = 0; p < ports; p++) + snd_seq_midisynth_delete(&msynth[p]); + kfree(msynth); + } + if (newclient) { + snd_seq_delete_kernel_client(client->seq_client); + kfree(client); + } + kfree(info); + kfree(port); + up(®ister_mutex); + return -ENOMEM; +} + +/* release midi synth port */ +static int +snd_seq_midisynth_unregister_port(snd_seq_device_t *dev) +{ + seq_midisynth_client_t *client; + seq_midisynth_t *msynth; + snd_card_t *card = dev->card; + int device = dev->device, p, ports; + + down(®ister_mutex); + client = synths[card->number]; + if (client == NULL || client->ports[device] == NULL) { + up(®ister_mutex); + return -ENODEV; + } + ports = client->ports_per_device[device]; + client->ports_per_device[device] = 0; + msynth = client->ports[device]; + client->ports[device] = NULL; + snd_runtime_check(msynth != NULL || ports <= 0, goto __skip); + for (p = 0; p < ports; p++) + snd_seq_midisynth_delete(&msynth[p]); + kfree(msynth); + __skip: + client->num_ports--; + if (client->num_ports <= 0) { + snd_seq_delete_kernel_client(client->seq_client); + synths[card->number] = NULL; + kfree(client); + } + up(®ister_mutex); + return 0; +} + + +static int __init alsa_seq_midi_init(void) +{ + static snd_seq_dev_ops_t ops = { + snd_seq_midisynth_register_port, + snd_seq_midisynth_unregister_port, + }; + memset(&synths, 0, sizeof(synths)); + snd_seq_autoload_lock(); + snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH, &ops, 0); + snd_seq_autoload_unlock(); + return 0; +} + +static void __exit alsa_seq_midi_exit(void) +{ + snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_MIDISYNTH); +} + +module_init(alsa_seq_midi_init) +module_exit(alsa_seq_midi_exit) diff --git a/sound/core/seq/seq_midi_emul.c b/sound/core/seq/seq_midi_emul.c new file mode 100644 index 000000000000..35fe8a7e34bf --- /dev/null +++ b/sound/core/seq/seq_midi_emul.c @@ -0,0 +1,735 @@ +/* + * GM/GS/XG midi module. + * + * Copyright (C) 1999 Steve Ratcliffe + * + * Based on awe_wave.c by Takashi Iwai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * This module is used to keep track of the current midi state. + * It can be used for drivers that are required to emulate midi when + * the hardware doesn't. + * + * It was written for a AWE64 driver, but there should be no AWE specific + * code in here. If there is it should be reported as a bug. + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <sound/core.h> +#include <sound/seq_kernel.h> +#include <sound/seq_midi_emul.h> +#include <sound/initval.h> +#include <sound/asoundef.h> + +MODULE_AUTHOR("Takashi Iwai / Steve Ratcliffe"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI emulation."); +MODULE_LICENSE("GPL"); + +/* Prototypes for static functions */ +static void note_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, int note, int vel); +static void do_control(snd_midi_op_t *ops, void *private, + snd_midi_channel_set_t *chset, snd_midi_channel_t *chan, + int control, int value); +static void rpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, snd_midi_channel_set_t *chset); +static void nrpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, snd_midi_channel_set_t *chset); +static void sysex(snd_midi_op_t *ops, void *private, unsigned char *sysex, int len, snd_midi_channel_set_t *chset); +static void all_sounds_off(snd_midi_op_t *ops, void *private, snd_midi_channel_t *chan); +static void all_notes_off(snd_midi_op_t *ops, void *private, snd_midi_channel_t *chan); +static void snd_midi_reset_controllers(snd_midi_channel_t *chan); +static void reset_all_channels(snd_midi_channel_set_t *chset); + + +/* + * Process an event in a driver independent way. This means dealing + * with RPN, NRPN, SysEx etc that are defined for common midi applications + * such as GM, GS and XG. + * There modes that this module will run in are: + * Generic MIDI - no interpretation at all, it will just save current values + * of controlers etc. + * GM - You can use all gm_ prefixed elements of chan. Controls, RPN, NRPN, + * SysEx will be interpreded as defined in General Midi. + * GS - You can use all gs_ prefixed elements of chan. Codes for GS will be + * interpreted. + * XG - You can use all xg_ prefixed elements of chan. Codes for XG will + * be interpreted. + */ +void +snd_midi_process_event(snd_midi_op_t *ops, + snd_seq_event_t *ev, snd_midi_channel_set_t *chanset) +{ + snd_midi_channel_t *chan; + void *drv; + int dest_channel = 0; + + if (ev == NULL || chanset == NULL) { + snd_printd("ev or chanbase NULL (snd_midi_process_event)\n"); + return; + } + if (chanset->channels == NULL) + return; + + if (snd_seq_ev_is_channel_type(ev)) { + dest_channel = ev->data.note.channel; + if (dest_channel >= chanset->max_channels) { + snd_printd("dest channel is %d, max is %d\n", dest_channel, chanset->max_channels); + return; + } + } + + chan = chanset->channels + dest_channel; + drv = chanset->private_data; + + /* EVENT_NOTE should be processed before queued */ + if (ev->type == SNDRV_SEQ_EVENT_NOTE) + return; + + /* Make sure that we don't have a note on that should really be + * a note off */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0) + ev->type = SNDRV_SEQ_EVENT_NOTEOFF; + + /* Make sure the note is within array range */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON || + ev->type == SNDRV_SEQ_EVENT_NOTEOFF || + ev->type == SNDRV_SEQ_EVENT_KEYPRESS) { + if (ev->data.note.note >= 128) + return; + } + + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEON: + if (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON) { + if (ops->note_off) + ops->note_off(drv, ev->data.note.note, 0, chan); + } + chan->note[ev->data.note.note] = SNDRV_MIDI_NOTE_ON; + if (ops->note_on) + ops->note_on(drv, ev->data.note.note, ev->data.note.velocity, chan); + break; + case SNDRV_SEQ_EVENT_NOTEOFF: + if (! (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON)) + break; + if (ops->note_off) + note_off(ops, drv, chan, ev->data.note.note, ev->data.note.velocity); + break; + case SNDRV_SEQ_EVENT_KEYPRESS: + if (ops->key_press) + ops->key_press(drv, ev->data.note.note, ev->data.note.velocity, chan); + break; + case SNDRV_SEQ_EVENT_CONTROLLER: + do_control(ops, drv, chanset, chan, + ev->data.control.param, ev->data.control.value); + break; + case SNDRV_SEQ_EVENT_PGMCHANGE: + chan->midi_program = ev->data.control.value; + break; + case SNDRV_SEQ_EVENT_PITCHBEND: + chan->midi_pitchbend = ev->data.control.value; + if (ops->control) + ops->control(drv, MIDI_CTL_PITCHBEND, chan); + break; + case SNDRV_SEQ_EVENT_CHANPRESS: + chan->midi_pressure = ev->data.control.value; + if (ops->control) + ops->control(drv, MIDI_CTL_CHAN_PRESSURE, chan); + break; + case SNDRV_SEQ_EVENT_CONTROL14: + /* Best guess is that this is any of the 14 bit controller values */ + if (ev->data.control.param < 32) { + /* set low part first */ + chan->control[ev->data.control.param + 32] = + ev->data.control.value & 0x7f; + do_control(ops, drv, chanset, chan, + ev->data.control.param, + ((ev->data.control.value>>7) & 0x7f)); + } else + do_control(ops, drv, chanset, chan, + ev->data.control.param, + ev->data.control.value); + break; + case SNDRV_SEQ_EVENT_NONREGPARAM: + /* Break it back into its controler values */ + chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED; + chan->control[MIDI_CTL_MSB_DATA_ENTRY] + = (ev->data.control.value >> 7) & 0x7f; + chan->control[MIDI_CTL_LSB_DATA_ENTRY] + = ev->data.control.value & 0x7f; + chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB] + = (ev->data.control.param >> 7) & 0x7f; + chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB] + = ev->data.control.param & 0x7f; + nrpn(ops, drv, chan, chanset); + break; + case SNDRV_SEQ_EVENT_REGPARAM: + /* Break it back into its controler values */ + chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED; + chan->control[MIDI_CTL_MSB_DATA_ENTRY] + = (ev->data.control.value >> 7) & 0x7f; + chan->control[MIDI_CTL_LSB_DATA_ENTRY] + = ev->data.control.value & 0x7f; + chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] + = (ev->data.control.param >> 7) & 0x7f; + chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB] + = ev->data.control.param & 0x7f; + rpn(ops, drv, chan, chanset); + break; + case SNDRV_SEQ_EVENT_SYSEX: + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE) { + unsigned char sysexbuf[64]; + int len; + len = snd_seq_expand_var_event(ev, sizeof(sysexbuf), sysexbuf, 1, 0); + if (len > 0) + sysex(ops, drv, sysexbuf, len, chanset); + } + break; + case SNDRV_SEQ_EVENT_SONGPOS: + case SNDRV_SEQ_EVENT_SONGSEL: + case SNDRV_SEQ_EVENT_CLOCK: + case SNDRV_SEQ_EVENT_START: + case SNDRV_SEQ_EVENT_CONTINUE: + case SNDRV_SEQ_EVENT_STOP: + case SNDRV_SEQ_EVENT_QFRAME: + case SNDRV_SEQ_EVENT_TEMPO: + case SNDRV_SEQ_EVENT_TIMESIGN: + case SNDRV_SEQ_EVENT_KEYSIGN: + goto not_yet; + case SNDRV_SEQ_EVENT_SENSING: + break; + case SNDRV_SEQ_EVENT_CLIENT_START: + case SNDRV_SEQ_EVENT_CLIENT_EXIT: + case SNDRV_SEQ_EVENT_CLIENT_CHANGE: + case SNDRV_SEQ_EVENT_PORT_START: + case SNDRV_SEQ_EVENT_PORT_EXIT: + case SNDRV_SEQ_EVENT_PORT_CHANGE: + case SNDRV_SEQ_EVENT_SAMPLE: + case SNDRV_SEQ_EVENT_SAMPLE_START: + case SNDRV_SEQ_EVENT_SAMPLE_STOP: + case SNDRV_SEQ_EVENT_SAMPLE_FREQ: + case SNDRV_SEQ_EVENT_SAMPLE_VOLUME: + case SNDRV_SEQ_EVENT_SAMPLE_LOOP: + case SNDRV_SEQ_EVENT_SAMPLE_POSITION: + case SNDRV_SEQ_EVENT_ECHO: + not_yet: + default: + /*snd_printd("Unimplemented event %d\n", ev->type);*/ + break; + } +} + + +/* + * release note + */ +static void +note_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, int note, int vel) +{ + if (chan->gm_hold) { + /* Hold this note until pedal is turned off */ + chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED; + } else if (chan->note[note] & SNDRV_MIDI_NOTE_SOSTENUTO) { + /* Mark this note as release; it will be turned off when sostenuto + * is turned off */ + chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED; + } else { + chan->note[note] = 0; + if (ops->note_off) + ops->note_off(drv, note, vel, chan); + } +} + +/* + * Do all driver independent operations for this controler and pass + * events that need to take place immediately to the driver. + */ +static void +do_control(snd_midi_op_t *ops, void *drv, snd_midi_channel_set_t *chset, + snd_midi_channel_t *chan, int control, int value) +{ + int i; + + /* Switches */ + if ((control >=64 && control <=69) || (control >= 80 && control <= 83)) { + /* These are all switches; either off or on so set to 0 or 127 */ + value = (value >= 64)? 127: 0; + } + chan->control[control] = value; + + switch (control) { + case MIDI_CTL_SUSTAIN: + if (value == 0) { + /* Sustain has been released, turn off held notes */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) { + chan->note[i] = SNDRV_MIDI_NOTE_OFF; + if (ops->note_off) + ops->note_off(drv, i, 0, chan); + } + } + } + break; + case MIDI_CTL_PORTAMENTO: + break; + case MIDI_CTL_SOSTENUTO: + if (value) { + /* Mark each note that is currently held down */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_ON) + chan->note[i] |= SNDRV_MIDI_NOTE_SOSTENUTO; + } + } else { + /* release all notes that were held */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_SOSTENUTO) { + chan->note[i] &= ~SNDRV_MIDI_NOTE_SOSTENUTO; + if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) { + chan->note[i] = SNDRV_MIDI_NOTE_OFF; + if (ops->note_off) + ops->note_off(drv, i, 0, chan); + } + } + } + } + break; + case MIDI_CTL_MSB_DATA_ENTRY: + chan->control[MIDI_CTL_LSB_DATA_ENTRY] = 0; + /* go through here */ + case MIDI_CTL_LSB_DATA_ENTRY: + if (chan->param_type == SNDRV_MIDI_PARAM_TYPE_REGISTERED) + rpn(ops, drv, chan, chset); + else + nrpn(ops, drv, chan, chset); + break; + case MIDI_CTL_REGIST_PARM_NUM_LSB: + case MIDI_CTL_REGIST_PARM_NUM_MSB: + chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED; + break; + case MIDI_CTL_NONREG_PARM_NUM_LSB: + case MIDI_CTL_NONREG_PARM_NUM_MSB: + chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED; + break; + + case MIDI_CTL_ALL_SOUNDS_OFF: + all_sounds_off(ops, drv, chan); + break; + + case MIDI_CTL_ALL_NOTES_OFF: + all_notes_off(ops, drv, chan); + break; + + case MIDI_CTL_MSB_BANK: + if (chset->midi_mode == SNDRV_MIDI_MODE_XG) { + if (value == 127) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } + break; + case MIDI_CTL_LSB_BANK: + break; + + case MIDI_CTL_RESET_CONTROLLERS: + snd_midi_reset_controllers(chan); + break; + + case MIDI_CTL_SOFT_PEDAL: + case MIDI_CTL_LEGATO_FOOTSWITCH: + case MIDI_CTL_HOLD2: + case MIDI_CTL_SC1_SOUND_VARIATION: + case MIDI_CTL_SC2_TIMBRE: + case MIDI_CTL_SC3_RELEASE_TIME: + case MIDI_CTL_SC4_ATTACK_TIME: + case MIDI_CTL_SC5_BRIGHTNESS: + case MIDI_CTL_E1_REVERB_DEPTH: + case MIDI_CTL_E2_TREMOLO_DEPTH: + case MIDI_CTL_E3_CHORUS_DEPTH: + case MIDI_CTL_E4_DETUNE_DEPTH: + case MIDI_CTL_E5_PHASER_DEPTH: + goto notyet; + notyet: + default: + if (ops->control) + ops->control(drv, control, chan); + break; + } +} + + +/* + * initialize the MIDI status + */ +void +snd_midi_channel_set_clear(snd_midi_channel_set_t *chset) +{ + int i; + + chset->midi_mode = SNDRV_MIDI_MODE_GM; + chset->gs_master_volume = 127; + + for (i = 0; i < chset->max_channels; i++) { + snd_midi_channel_t *chan = chset->channels + i; + memset(chan->note, 0, sizeof(chan->note)); + + chan->midi_aftertouch = 0; + chan->midi_pressure = 0; + chan->midi_program = 0; + chan->midi_pitchbend = 0; + snd_midi_reset_controllers(chan); + chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + chan->gm_rpn_fine_tuning = 0; + chan->gm_rpn_coarse_tuning = 0; + + if (i == 9) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } +} + +/* + * Process a rpn message. + */ +static void +rpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, + snd_midi_channel_set_t *chset) +{ + int type; + int val; + + if (chset->midi_mode != SNDRV_MIDI_MODE_NONE) { + type = (chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] << 8) | + chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB]; + val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) | + chan->control[MIDI_CTL_LSB_DATA_ENTRY]; + + switch (type) { + case 0x0000: /* Pitch bend sensitivity */ + /* MSB only / 1 semitone per 128 */ + chan->gm_rpn_pitch_bend_range = val; + break; + + case 0x0001: /* fine tuning: */ + /* MSB/LSB, 8192=center, 100/8192 cent step */ + chan->gm_rpn_fine_tuning = val - 8192; + break; + + case 0x0002: /* coarse tuning */ + /* MSB only / 8192=center, 1 semitone per 128 */ + chan->gm_rpn_coarse_tuning = val - 8192; + break; + + case 0x7F7F: /* "lock-in" RPN */ + /* ignored */ + break; + } + } + /* should call nrpn or rpn callback here.. */ +} + +/* + * Process an nrpn message. + */ +static void +nrpn(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan, + snd_midi_channel_set_t *chset) +{ + /* parse XG NRPNs here if possible */ + if (ops->nrpn) + ops->nrpn(drv, chan, chset); +} + + +/* + * convert channel parameter in GS sysex + */ +static int +get_channel(unsigned char cmd) +{ + int p = cmd & 0x0f; + if (p == 0) + p = 9; + else if (p < 10) + p--; + return p; +} + + +/* + * Process a sysex message. + */ +static void +sysex(snd_midi_op_t *ops, void *private, unsigned char *buf, int len, snd_midi_channel_set_t *chset) +{ + /* GM on */ + static unsigned char gm_on_macro[] = { + 0x7e,0x7f,0x09,0x01, + }; + /* XG on */ + static unsigned char xg_on_macro[] = { + 0x43,0x10,0x4c,0x00,0x00,0x7e,0x00, + }; + /* GS prefix + * drum channel: XX=0x1?(channel), YY=0x15, ZZ=on/off + * reverb mode: XX=0x01, YY=0x30, ZZ=0-7 + * chorus mode: XX=0x01, YY=0x38, ZZ=0-7 + * master vol: XX=0x00, YY=0x04, ZZ=0-127 + */ + static unsigned char gs_pfx_macro[] = { + 0x41,0x10,0x42,0x12,0x40,/*XX,YY,ZZ*/ + }; + + int parsed = SNDRV_MIDI_SYSEX_NOT_PARSED; + + if (len <= 0 || buf[0] != 0xf0) + return; + /* skip first byte */ + buf++; + len--; + + /* GM on */ + if (len >= (int)sizeof(gm_on_macro) && + memcmp(buf, gm_on_macro, sizeof(gm_on_macro)) == 0) { + if (chset->midi_mode != SNDRV_MIDI_MODE_GS && + chset->midi_mode != SNDRV_MIDI_MODE_XG) { + chset->midi_mode = SNDRV_MIDI_MODE_GM; + reset_all_channels(chset); + parsed = SNDRV_MIDI_SYSEX_GM_ON; + } + } + + /* GS macros */ + else if (len >= 8 && + memcmp(buf, gs_pfx_macro, sizeof(gs_pfx_macro)) == 0) { + if (chset->midi_mode != SNDRV_MIDI_MODE_GS && + chset->midi_mode != SNDRV_MIDI_MODE_XG) + chset->midi_mode = SNDRV_MIDI_MODE_GS; + + if (buf[5] == 0x00 && buf[6] == 0x7f && buf[7] == 0x00) { + /* GS reset */ + parsed = SNDRV_MIDI_SYSEX_GS_RESET; + reset_all_channels(chset); + } + + else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x15) { + /* drum pattern */ + int p = get_channel(buf[5]); + if (p < chset->max_channels) { + parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL; + if (buf[7]) + chset->channels[p].drum_channel = 1; + else + chset->channels[p].drum_channel = 0; + } + + } else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x21) { + /* program */ + int p = get_channel(buf[5]); + if (p < chset->max_channels && + ! chset->channels[p].drum_channel) { + parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL; + chset->channels[p].midi_program = buf[7]; + } + + } else if (buf[5] == 0x01 && buf[6] == 0x30) { + /* reverb mode */ + parsed = SNDRV_MIDI_SYSEX_GS_REVERB_MODE; + chset->gs_reverb_mode = buf[7]; + + } else if (buf[5] == 0x01 && buf[6] == 0x38) { + /* chorus mode */ + parsed = SNDRV_MIDI_SYSEX_GS_CHORUS_MODE; + chset->gs_chorus_mode = buf[7]; + + } else if (buf[5] == 0x00 && buf[6] == 0x04) { + /* master volume */ + parsed = SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME; + chset->gs_master_volume = buf[7]; + + } + } + + /* XG on */ + else if (len >= (int)sizeof(xg_on_macro) && + memcmp(buf, xg_on_macro, sizeof(xg_on_macro)) == 0) { + int i; + chset->midi_mode = SNDRV_MIDI_MODE_XG; + parsed = SNDRV_MIDI_SYSEX_XG_ON; + /* reset CC#0 for drums */ + for (i = 0; i < chset->max_channels; i++) { + if (chset->channels[i].drum_channel) + chset->channels[i].control[MIDI_CTL_MSB_BANK] = 127; + else + chset->channels[i].control[MIDI_CTL_MSB_BANK] = 0; + } + } + + if (ops->sysex) + ops->sysex(private, buf - 1, len + 1, parsed, chset); +} + +/* + * all sound off + */ +static void +all_sounds_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan) +{ + int n; + + if (! ops->note_terminate) + return; + for (n = 0; n < 128; n++) { + if (chan->note[n]) { + ops->note_terminate(drv, n, chan); + chan->note[n] = 0; + } + } +} + +/* + * all notes off + */ +static void +all_notes_off(snd_midi_op_t *ops, void *drv, snd_midi_channel_t *chan) +{ + int n; + + if (! ops->note_off) + return; + for (n = 0; n < 128; n++) { + if (chan->note[n] == SNDRV_MIDI_NOTE_ON) + note_off(ops, drv, chan, n, 0); + } +} + +/* + * Initialise a single midi channel control block. + */ +static void snd_midi_channel_init(snd_midi_channel_t *p, int n) +{ + if (p == NULL) + return; + + memset(p, 0, sizeof(snd_midi_channel_t)); + p->private = NULL; + p->number = n; + + snd_midi_reset_controllers(p); + p->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + p->gm_rpn_fine_tuning = 0; + p->gm_rpn_coarse_tuning = 0; + + if (n == 9) + p->drum_channel = 1; /* Default ch 10 as drums */ +} + +/* + * Allocate and initialise a set of midi channel control blocks. + */ +static snd_midi_channel_t *snd_midi_channel_init_set(int n) +{ + snd_midi_channel_t *chan; + int i; + + chan = kmalloc(n * sizeof(snd_midi_channel_t), GFP_KERNEL); + if (chan) { + for (i = 0; i < n; i++) + snd_midi_channel_init(chan+i, i); + } + + return chan; +} + +/* + * reset all midi channels + */ +static void +reset_all_channels(snd_midi_channel_set_t *chset) +{ + int ch; + for (ch = 0; ch < chset->max_channels; ch++) { + snd_midi_channel_t *chan = chset->channels + ch; + snd_midi_reset_controllers(chan); + chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + chan->gm_rpn_fine_tuning = 0; + chan->gm_rpn_coarse_tuning = 0; + + if (ch == 9) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } +} + + +/* + * Allocate and initialise a midi channel set. + */ +snd_midi_channel_set_t *snd_midi_channel_alloc_set(int n) +{ + snd_midi_channel_set_t *chset; + + chset = kmalloc(sizeof(*chset), GFP_KERNEL); + if (chset) { + chset->channels = snd_midi_channel_init_set(n); + chset->private_data = NULL; + chset->max_channels = n; + } + return chset; +} + +/* + * Reset the midi controllers on a particular channel to default values. + */ +static void snd_midi_reset_controllers(snd_midi_channel_t *chan) +{ + memset(chan->control, 0, sizeof(chan->control)); + chan->gm_volume = 127; + chan->gm_expression = 127; + chan->gm_pan = 64; +} + + +/* + * Free a midi channel set. + */ +void snd_midi_channel_free_set(snd_midi_channel_set_t *chset) +{ + if (chset == NULL) + return; + kfree(chset->channels); + kfree(chset); +} + +static int __init alsa_seq_midi_emul_init(void) +{ + return 0; +} + +static void __exit alsa_seq_midi_emul_exit(void) +{ +} + +module_init(alsa_seq_midi_emul_init) +module_exit(alsa_seq_midi_emul_exit) + +EXPORT_SYMBOL(snd_midi_process_event); +EXPORT_SYMBOL(snd_midi_channel_set_clear); +EXPORT_SYMBOL(snd_midi_channel_alloc_set); +EXPORT_SYMBOL(snd_midi_channel_free_set); diff --git a/sound/core/seq/seq_midi_event.c b/sound/core/seq/seq_midi_event.c new file mode 100644 index 000000000000..21e569062bc3 --- /dev/null +++ b/sound/core/seq/seq_midi_event.c @@ -0,0 +1,539 @@ +/* + * MIDI byte <-> sequencer event coder + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>, + * Jaroslav Kysela <perex@suse.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <sound/driver.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <sound/core.h> +#include <sound/seq_kernel.h> +#include <sound/seq_midi_event.h> +#include <sound/asoundef.h> + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("MIDI byte <-> sequencer event coder"); +MODULE_LICENSE("GPL"); + +/* queue type */ +/* from 0 to 7 are normal commands (note off, on, etc.) */ +#define ST_NOTEOFF 0 +#define ST_NOTEON 1 +#define ST_SPECIAL 8 +#define ST_SYSEX ST_SPECIAL +/* from 8 to 15 are events for 0xf0-0xf7 */ + + +/* status event types */ +typedef void (*event_encode_t)(snd_midi_event_t *dev, snd_seq_event_t *ev); +typedef void (*event_decode_t)(snd_seq_event_t *ev, unsigned char *buf); + +/* + * prototypes + */ +static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev); +static void note_decode(snd_seq_event_t *ev, unsigned char *buf); +static void one_param_decode(snd_seq_event_t *ev, unsigned char *buf); +static void pitchbend_decode(snd_seq_event_t *ev, unsigned char *buf); +static void two_param_decode(snd_seq_event_t *ev, unsigned char *buf); +static void songpos_decode(snd_seq_event_t *ev, unsigned char *buf); + +/* + * event list + */ +static struct status_event_list_t { + int event; + int qlen; + event_encode_t encode; + event_decode_t decode; +} status_event[] = { + /* 0x80 - 0xf0 */ + {SNDRV_SEQ_EVENT_NOTEOFF, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_NOTEON, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_KEYPRESS, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_CONTROLLER, 2, two_param_ctrl_event, two_param_decode}, + {SNDRV_SEQ_EVENT_PGMCHANGE, 1, one_param_ctrl_event, one_param_decode}, + {SNDRV_SEQ_EVENT_CHANPRESS, 1, one_param_ctrl_event, one_param_decode}, + {SNDRV_SEQ_EVENT_PITCHBEND, 2, pitchbend_ctrl_event, pitchbend_decode}, + {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf0 */ + /* 0xf0 - 0xff */ + {SNDRV_SEQ_EVENT_SYSEX, 1, NULL, NULL}, /* sysex: 0xf0 */ + {SNDRV_SEQ_EVENT_QFRAME, 1, one_param_event, one_param_decode}, /* 0xf1 */ + {SNDRV_SEQ_EVENT_SONGPOS, 2, songpos_event, songpos_decode}, /* 0xf2 */ + {SNDRV_SEQ_EVENT_SONGSEL, 1, one_param_event, one_param_decode}, /* 0xf3 */ + {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf4 */ + {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf5 */ + {SNDRV_SEQ_EVENT_TUNE_REQUEST, 0, NULL, NULL}, /* 0xf6 */ + {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf7 */ + {SNDRV_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */ + {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xf9 */ + {SNDRV_SEQ_EVENT_START, 0, NULL, NULL}, /* 0xfa */ + {SNDRV_SEQ_EVENT_CONTINUE, 0, NULL, NULL}, /* 0xfb */ + {SNDRV_SEQ_EVENT_STOP, 0, NULL, NULL}, /* 0xfc */ + {SNDRV_SEQ_EVENT_NONE, 0, NULL, NULL}, /* 0xfd */ + {SNDRV_SEQ_EVENT_SENSING, 0, NULL, NULL}, /* 0xfe */ + {SNDRV_SEQ_EVENT_RESET, 0, NULL, NULL}, /* 0xff */ +}; + +static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int len, snd_seq_event_t *ev); +static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev); + +static struct extra_event_list_t { + int event; + int (*decode)(snd_midi_event_t *dev, unsigned char *buf, int len, snd_seq_event_t *ev); +} extra_event[] = { + {SNDRV_SEQ_EVENT_CONTROL14, extra_decode_ctrl14}, + {SNDRV_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn}, + {SNDRV_SEQ_EVENT_REGPARAM, extra_decode_xrpn}, +}; + +/* + * new/delete record + */ + +int snd_midi_event_new(int bufsize, snd_midi_event_t **rdev) +{ + snd_midi_event_t *dev; + + *rdev = NULL; + dev = kcalloc(1, sizeof(*dev), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + if (bufsize > 0) { + dev->buf = kmalloc(bufsize, GFP_KERNEL); + if (dev->buf == NULL) { + kfree(dev); + return -ENOMEM; + } + } + dev->bufsize = bufsize; + dev->lastcmd = 0xff; + spin_lock_init(&dev->lock); + *rdev = dev; + return 0; +} + +void snd_midi_event_free(snd_midi_event_t *dev) +{ + if (dev != NULL) { + kfree(dev->buf); + kfree(dev); + } +} + +/* + * initialize record + */ +inline static void reset_encode(snd_midi_event_t *dev) +{ + dev->read = 0; + dev->qlen = 0; + dev->type = 0; +} + +void snd_midi_event_reset_encode(snd_midi_event_t *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + reset_encode(dev); + spin_unlock_irqrestore(&dev->lock, flags); +} + +void snd_midi_event_reset_decode(snd_midi_event_t *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dev->lastcmd = 0xff; + spin_unlock_irqrestore(&dev->lock, flags); +} + +void snd_midi_event_init(snd_midi_event_t *dev) +{ + snd_midi_event_reset_encode(dev); + snd_midi_event_reset_decode(dev); +} + +void snd_midi_event_no_status(snd_midi_event_t *dev, int on) +{ + dev->nostat = on ? 1 : 0; +} + +/* + * resize buffer + */ +int snd_midi_event_resize_buffer(snd_midi_event_t *dev, int bufsize) +{ + unsigned char *new_buf, *old_buf; + unsigned long flags; + + if (bufsize == dev->bufsize) + return 0; + new_buf = kmalloc(bufsize, GFP_KERNEL); + if (new_buf == NULL) + return -ENOMEM; + spin_lock_irqsave(&dev->lock, flags); + old_buf = dev->buf; + dev->buf = new_buf; + dev->bufsize = bufsize; + reset_encode(dev); + spin_unlock_irqrestore(&dev->lock, flags); + kfree(old_buf); + return 0; +} + +/* + * read bytes and encode to sequencer event if finished + * return the size of encoded bytes + */ +long snd_midi_event_encode(snd_midi_event_t *dev, unsigned char *buf, long count, snd_seq_event_t *ev) +{ + long result = 0; + int rc; + + ev->type = SNDRV_SEQ_EVENT_NONE; + + while (count-- > 0) { + rc = snd_midi_event_encode_byte(dev, *buf++, ev); + result++; + if (rc < 0) + return rc; + else if (rc > 0) + return result; + } + + return result; +} + +/* + * read one byte and encode to sequencer event: + * return 1 if MIDI bytes are encoded to an event + * 0 data is not finished + * negative for error + */ +int snd_midi_event_encode_byte(snd_midi_event_t *dev, int c, snd_seq_event_t *ev) +{ + int rc = 0; + unsigned long flags; + + c &= 0xff; + + if (c >= MIDI_CMD_COMMON_CLOCK) { + /* real-time event */ + ev->type = status_event[ST_SPECIAL + c - 0xf0].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + return 1; + } + + spin_lock_irqsave(&dev->lock, flags); + if (dev->qlen > 0) { + /* rest of command */ + dev->buf[dev->read++] = c; + if (dev->type != ST_SYSEX) + dev->qlen--; + } else { + /* new command */ + dev->read = 1; + if (c & 0x80) { + dev->buf[0] = c; + if ((c & 0xf0) == 0xf0) /* special events */ + dev->type = (c & 0x0f) + ST_SPECIAL; + else + dev->type = (c >> 4) & 0x07; + dev->qlen = status_event[dev->type].qlen; + } else { + /* process this byte as argument */ + dev->buf[dev->read++] = c; + dev->qlen = status_event[dev->type].qlen - 1; + } + } + if (dev->qlen == 0) { + ev->type = status_event[dev->type].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + if (status_event[dev->type].encode) /* set data values */ + status_event[dev->type].encode(dev, ev); + rc = 1; + } else if (dev->type == ST_SYSEX) { + if (c == MIDI_CMD_COMMON_SYSEX_END || + dev->read >= dev->bufsize) { + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + ev->type = SNDRV_SEQ_EVENT_SYSEX; + ev->data.ext.len = dev->read; + ev->data.ext.ptr = dev->buf; + if (c != MIDI_CMD_COMMON_SYSEX_END) + dev->read = 0; /* continue to parse */ + else + reset_encode(dev); /* all parsed */ + rc = 1; + } + } + + spin_unlock_irqrestore(&dev->lock, flags); + return rc; +} + +/* encode note event */ +static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.note.channel = dev->buf[0] & 0x0f; + ev->data.note.note = dev->buf[1]; + ev->data.note.velocity = dev->buf[2]; +} + +/* encode one parameter controls */ +static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = dev->buf[1]; +} + +/* encode pitch wheel change */ +static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192; +} + +/* encode midi control change */ +static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.param = dev->buf[1]; + ev->data.control.value = dev->buf[2]; +} + +/* encode one parameter value*/ +static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.value = dev->buf[1]; +} + +/* encode song position */ +static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev) +{ + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1]; +} + +/* + * decode from a sequencer event to midi bytes + * return the size of decoded midi events + */ +long snd_midi_event_decode(snd_midi_event_t *dev, unsigned char *buf, long count, snd_seq_event_t *ev) +{ + unsigned int cmd, type; + + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return -ENOENT; + + for (type = 0; type < ARRAY_SIZE(status_event); type++) { + if (ev->type == status_event[type].event) + goto __found; + } + for (type = 0; type < ARRAY_SIZE(extra_event); type++) { + if (ev->type == extra_event[type].event) + return extra_event[type].decode(dev, buf, count, ev); + } + return -ENOENT; + + __found: + if (type >= ST_SPECIAL) + cmd = 0xf0 + (type - ST_SPECIAL); + else + /* data.note.channel and data.control.channel is identical */ + cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f); + + + if (cmd == MIDI_CMD_COMMON_SYSEX) { + snd_midi_event_reset_decode(dev); + return snd_seq_expand_var_event(ev, count, buf, 1, 0); + } else { + int qlen; + unsigned char xbuf[4]; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) { + dev->lastcmd = cmd; + spin_unlock_irqrestore(&dev->lock, flags); + xbuf[0] = cmd; + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 1); + qlen = status_event[type].qlen + 1; + } else { + spin_unlock_irqrestore(&dev->lock, flags); + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 0); + qlen = status_event[type].qlen; + } + if (count < qlen) + return -ENOMEM; + memcpy(buf, xbuf, qlen); + return qlen; + } +} + + +/* decode note event */ +static void note_decode(snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.note.note & 0x7f; + buf[1] = ev->data.note.velocity & 0x7f; +} + +/* decode one parameter controls */ +static void one_param_decode(snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; +} + +/* decode pitch wheel change */ +static void pitchbend_decode(snd_seq_event_t *ev, unsigned char *buf) +{ + int value = ev->data.control.value + 8192; + buf[0] = value & 0x7f; + buf[1] = (value >> 7) & 0x7f; +} + +/* decode midi control change */ +static void two_param_decode(snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.param & 0x7f; + buf[1] = ev->data.control.value & 0x7f; +} + +/* decode song position */ +static void songpos_decode(snd_seq_event_t *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; + buf[1] = (ev->data.control.value >> 7) & 0x7f; +} + +/* decode 14bit control */ +static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev) +{ + unsigned char cmd; + int idx = 0; + + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + if (ev->data.control.param < 0x20) { + if (count < 4) + return -ENOMEM; + if (dev->nostat && count < 6) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 5) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param; + buf[idx++] = (ev->data.control.value >> 7) & 0x7f; + if (dev->nostat) + buf[idx++] = cmd; + buf[idx++] = ev->data.control.param + 0x20; + buf[idx++] = ev->data.control.value & 0x7f; + } else { + if (count < 2) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 3) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param & 0x7f; + buf[idx++] = ev->data.control.value & 0x7f; + } + return idx; +} + +/* decode reg/nonreg param */ +static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, snd_seq_event_t *ev) +{ + unsigned char cmd; + char *cbytes; + static char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB, + MIDI_CTL_NONREG_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + static char cbytes_rpn[4] = { MIDI_CTL_REGIST_PARM_NUM_MSB, + MIDI_CTL_REGIST_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + unsigned char bytes[4]; + int idx = 0, i; + + if (count < 8) + return -ENOMEM; + if (dev->nostat && count < 12) + return -ENOMEM; + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + bytes[0] = ev->data.control.param & 0x007f; + bytes[1] = (ev->data.control.param & 0x3f80) >> 7; + bytes[2] = ev->data.control.value & 0x007f; + bytes[3] = (ev->data.control.value & 0x3f80) >> 7; + if (cmd != dev->lastcmd && !dev->nostat) { + if (count < 9) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + cbytes = ev->type == SNDRV_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn; + for (i = 0; i < 4; i++) { + if (dev->nostat) + buf[idx++] = dev->lastcmd = cmd; + buf[idx++] = cbytes[i]; + buf[idx++] = bytes[i]; + } + return idx; +} + +/* + * exports + */ + +EXPORT_SYMBOL(snd_midi_event_new); +EXPORT_SYMBOL(snd_midi_event_free); +EXPORT_SYMBOL(snd_midi_event_resize_buffer); +EXPORT_SYMBOL(snd_midi_event_init); +EXPORT_SYMBOL(snd_midi_event_reset_encode); +EXPORT_SYMBOL(snd_midi_event_reset_decode); +EXPORT_SYMBOL(snd_midi_event_no_status); +EXPORT_SYMBOL(snd_midi_event_encode); +EXPORT_SYMBOL(snd_midi_event_encode_byte); +EXPORT_SYMBOL(snd_midi_event_decode); + +static int __init alsa_seq_midi_event_init(void) +{ + return 0; +} + +static void __exit alsa_seq_midi_event_exit(void) +{ +} + +module_init(alsa_seq_midi_event_init) +module_exit(alsa_seq_midi_event_exit) diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c new file mode 100644 index 000000000000..b976951fc100 --- /dev/null +++ b/sound/core/seq/seq_ports.c @@ -0,0 +1,674 @@ +/* + * ALSA sequencer Ports + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <sound/core.h> +#include <linux/slab.h> +#include "seq_system.h" +#include "seq_ports.h" +#include "seq_clientmgr.h" + +/* + + registration of client ports + + */ + + +/* + +NOTE: the current implementation of the port structure as a linked list is +not optimal for clients that have many ports. For sending messages to all +subscribers of a port we first need to find the address of the port +structure, which means we have to traverse the list. A direct access table +(array) would be better, but big preallocated arrays waste memory. + +Possible actions: + +1) leave it this way, a client does normaly does not have more than a few +ports + +2) replace the linked list of ports by a array of pointers which is +dynamicly kmalloced. When a port is added or deleted we can simply allocate +a new array, copy the corresponding pointers, and delete the old one. We +then only need a pointer to this array, and an integer that tells us how +much elements are in array. + +*/ + +/* return pointer to port structure - port is locked if found */ +client_port_t *snd_seq_port_use_ptr(client_t *client, int num) +{ + struct list_head *p; + client_port_t *port; + + if (client == NULL) + return NULL; + read_lock(&client->ports_lock); + list_for_each(p, &client->ports_list_head) { + port = list_entry(p, client_port_t, list); + if (port->addr.port == num) { + if (port->closing) + break; /* deleting now */ + snd_use_lock_use(&port->use_lock); + read_unlock(&client->ports_lock); + return port; + } + } + read_unlock(&client->ports_lock); + return NULL; /* not found */ +} + + +/* search for the next port - port is locked if found */ +client_port_t *snd_seq_port_query_nearest(client_t *client, snd_seq_port_info_t *pinfo) +{ + int num; + struct list_head *p; + client_port_t *port, *found; + + num = pinfo->addr.port; + found = NULL; + read_lock(&client->ports_lock); + list_for_each(p, &client->ports_list_head) { + port = list_entry(p, client_port_t, list); + if (port->addr.port < num) + continue; + if (port->addr.port == num) { + found = port; + break; + } + if (found == NULL || port->addr.port < found->addr.port) + found = port; + } + if (found) { + if (found->closing) + found = NULL; + else + snd_use_lock_use(&found->use_lock); + } + read_unlock(&client->ports_lock); + return found; +} + + +/* initialize port_subs_info_t */ +static void port_subs_info_init(port_subs_info_t *grp) +{ + INIT_LIST_HEAD(&grp->list_head); + grp->count = 0; + grp->exclusive = 0; + rwlock_init(&grp->list_lock); + init_rwsem(&grp->list_mutex); + grp->open = NULL; + grp->close = NULL; +} + + +/* create a port, port number is returned (-1 on failure) */ +client_port_t *snd_seq_create_port(client_t *client, int port) +{ + unsigned long flags; + client_port_t *new_port; + struct list_head *l; + int num = -1; + + /* sanity check */ + snd_assert(client, return NULL); + + if (client->num_ports >= SNDRV_SEQ_MAX_PORTS - 1) { + snd_printk(KERN_WARNING "too many ports for client %d\n", client->number); + return NULL; + } + + /* create a new port */ + new_port = kcalloc(1, sizeof(*new_port), GFP_KERNEL); + if (! new_port) { + snd_printd("malloc failed for registering client port\n"); + return NULL; /* failure, out of memory */ + } + /* init port data */ + new_port->addr.client = client->number; + new_port->addr.port = -1; + new_port->owner = THIS_MODULE; + sprintf(new_port->name, "port-%d", num); + snd_use_lock_init(&new_port->use_lock); + port_subs_info_init(&new_port->c_src); + port_subs_info_init(&new_port->c_dest); + + num = port >= 0 ? port : 0; + down(&client->ports_mutex); + write_lock_irqsave(&client->ports_lock, flags); + list_for_each(l, &client->ports_list_head) { + client_port_t *p = list_entry(l, client_port_t, list); + if (p->addr.port > num) + break; + if (port < 0) /* auto-probe mode */ + num = p->addr.port + 1; + } + /* insert the new port */ + list_add_tail(&new_port->list, l); + client->num_ports++; + new_port->addr.port = num; /* store the port number in the port */ + write_unlock_irqrestore(&client->ports_lock, flags); + up(&client->ports_mutex); + sprintf(new_port->name, "port-%d", num); + + return new_port; +} + +/* */ +enum group_type_t { + SRC_LIST, DEST_LIST +}; + +static int subscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp, snd_seq_port_subscribe_t *info, int send_ack); +static int unsubscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp, snd_seq_port_subscribe_t *info, int send_ack); + + +static client_port_t *get_client_port(snd_seq_addr_t *addr, client_t **cp) +{ + client_port_t *p; + *cp = snd_seq_client_use_ptr(addr->client); + if (*cp) { + p = snd_seq_port_use_ptr(*cp, addr->port); + if (! p) { + snd_seq_client_unlock(*cp); + *cp = NULL; + } + return p; + } + return NULL; +} + +/* + * remove all subscribers on the list + * this is called from port_delete, for each src and dest list. + */ +static void clear_subscriber_list(client_t *client, client_port_t *port, + port_subs_info_t *grp, int grptype) +{ + struct list_head *p, *n; + + down_write(&grp->list_mutex); + list_for_each_safe(p, n, &grp->list_head) { + subscribers_t *subs; + client_t *c; + client_port_t *aport; + + if (grptype == SRC_LIST) { + subs = list_entry(p, subscribers_t, src_list); + aport = get_client_port(&subs->info.dest, &c); + } else { + subs = list_entry(p, subscribers_t, dest_list); + aport = get_client_port(&subs->info.sender, &c); + } + list_del(p); + unsubscribe_port(client, port, grp, &subs->info, 0); + if (!aport) { + /* looks like the connected port is being deleted. + * we decrease the counter, and when both ports are deleted + * remove the subscriber info + */ + if (atomic_dec_and_test(&subs->ref_count)) + kfree(subs); + } else { + /* ok we got the connected port */ + port_subs_info_t *agrp; + agrp = (grptype == SRC_LIST) ? &aport->c_dest : &aport->c_src; + down_write(&agrp->list_mutex); + if (grptype == SRC_LIST) + list_del(&subs->dest_list); + else + list_del(&subs->src_list); + unsubscribe_port(c, aport, agrp, &subs->info, 1); + kfree(subs); + up_write(&agrp->list_mutex); + snd_seq_port_unlock(aport); + snd_seq_client_unlock(c); + } + } + up_write(&grp->list_mutex); +} + +/* delete port data */ +static int port_delete(client_t *client, client_port_t *port) +{ + /* set closing flag and wait for all port access are gone */ + port->closing = 1; + snd_use_lock_sync(&port->use_lock); + + /* clear subscribers info */ + clear_subscriber_list(client, port, &port->c_src, SRC_LIST); + clear_subscriber_list(client, port, &port->c_dest, DEST_LIST); + + if (port->private_free) + port->private_free(port->private_data); + + snd_assert(port->c_src.count == 0,); + snd_assert(port->c_dest.count == 0,); + + kfree(port); + return 0; +} + + +/* delete a port with the given port id */ +int snd_seq_delete_port(client_t *client, int port) +{ + unsigned long flags; + struct list_head *l; + client_port_t *found = NULL; + + down(&client->ports_mutex); + write_lock_irqsave(&client->ports_lock, flags); + list_for_each(l, &client->ports_list_head) { + client_port_t *p = list_entry(l, client_port_t, list); + if (p->addr.port == port) { + /* ok found. delete from the list at first */ + list_del(l); + client->num_ports--; + found = p; + break; + } + } + write_unlock_irqrestore(&client->ports_lock, flags); + up(&client->ports_mutex); + if (found) + return port_delete(client, found); + else + return -ENOENT; +} + +/* delete the all ports belonging to the given client */ +int snd_seq_delete_all_ports(client_t *client) +{ + unsigned long flags; + struct list_head deleted_list, *p, *n; + + /* move the port list to deleted_list, and + * clear the port list in the client data. + */ + down(&client->ports_mutex); + write_lock_irqsave(&client->ports_lock, flags); + if (! list_empty(&client->ports_list_head)) { + __list_add(&deleted_list, + client->ports_list_head.prev, + client->ports_list_head.next); + INIT_LIST_HEAD(&client->ports_list_head); + } else { + INIT_LIST_HEAD(&deleted_list); + } + client->num_ports = 0; + write_unlock_irqrestore(&client->ports_lock, flags); + + /* remove each port in deleted_list */ + list_for_each_safe(p, n, &deleted_list) { + client_port_t *port = list_entry(p, client_port_t, list); + list_del(p); + snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); + port_delete(client, port); + } + up(&client->ports_mutex); + return 0; +} + +/* set port info fields */ +int snd_seq_set_port_info(client_port_t * port, snd_seq_port_info_t * info) +{ + snd_assert(port && info, return -EINVAL); + + /* set port name */ + if (info->name[0]) + strlcpy(port->name, info->name, sizeof(port->name)); + + /* set capabilities */ + port->capability = info->capability; + + /* get port type */ + port->type = info->type; + + /* information about supported channels/voices */ + port->midi_channels = info->midi_channels; + port->midi_voices = info->midi_voices; + port->synth_voices = info->synth_voices; + + /* timestamping */ + port->timestamping = (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0; + port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0; + port->time_queue = info->time_queue; + + return 0; +} + +/* get port info fields */ +int snd_seq_get_port_info(client_port_t * port, snd_seq_port_info_t * info) +{ + snd_assert(port && info, return -EINVAL); + + /* get port name */ + strlcpy(info->name, port->name, sizeof(info->name)); + + /* get capabilities */ + info->capability = port->capability; + + /* get port type */ + info->type = port->type; + + /* information about supported channels/voices */ + info->midi_channels = port->midi_channels; + info->midi_voices = port->midi_voices; + info->synth_voices = port->synth_voices; + + /* get subscriber counts */ + info->read_use = port->c_src.count; + info->write_use = port->c_dest.count; + + /* timestamping */ + info->flags = 0; + if (port->timestamping) { + info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP; + if (port->time_real) + info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL; + info->time_queue = port->time_queue; + } + + return 0; +} + + + +/* + * call callback functions (if any): + * the callbacks are invoked only when the first (for connection) or + * the last subscription (for disconnection) is done. Second or later + * subscription results in increment of counter, but no callback is + * invoked. + * This feature is useful if these callbacks are associated with + * initialization or termination of devices (see seq_midi.c). + * + * If callback_all option is set, the callback function is invoked + * at each connnection/disconnection. + */ + +static int subscribe_port(client_t *client, client_port_t *port, port_subs_info_t *grp, + snd_seq_port_subscribe_t *info, int send_ack) +{ + int err = 0; + + if (!try_module_get(port->owner)) + return -EFAULT; + grp->count++; + if (grp->open && (port->callback_all || grp->count == 1)) { + err = grp->open(port->private_data, info); + if (err < 0) { + module_put(port->owner); + grp->count--; + } + } + if (err >= 0 && send_ack && client->type == USER_CLIENT) + snd_seq_client_notify_subscription(port->addr.client, port->addr.port, + info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED); + + return err; +} + +static int unsubscribe_port(client_t *client, client_port_t *port, + port_subs_info_t *grp, + snd_seq_port_subscribe_t *info, int send_ack) +{ + int err = 0; + + if (! grp->count) + return -EINVAL; + grp->count--; + if (grp->close && (port->callback_all || grp->count == 0)) + err = grp->close(port->private_data, info); + if (send_ack && client->type == USER_CLIENT) + snd_seq_client_notify_subscription(port->addr.client, port->addr.port, + info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED); + module_put(port->owner); + return err; +} + + + +/* check if both addresses are identical */ +static inline int addr_match(snd_seq_addr_t *r, snd_seq_addr_t *s) +{ + return (r->client == s->client) && (r->port == s->port); +} + +/* check the two subscribe info match */ +/* if flags is zero, checks only sender and destination addresses */ +static int match_subs_info(snd_seq_port_subscribe_t *r, + snd_seq_port_subscribe_t *s) +{ + if (addr_match(&r->sender, &s->sender) && + addr_match(&r->dest, &s->dest)) { + if (r->flags && r->flags == s->flags) + return r->queue == s->queue; + else if (! r->flags) + return 1; + } + return 0; +} + + +/* connect two ports */ +int snd_seq_port_connect(client_t *connector, + client_t *src_client, client_port_t *src_port, + client_t *dest_client, client_port_t *dest_port, + snd_seq_port_subscribe_t *info) +{ + port_subs_info_t *src = &src_port->c_src; + port_subs_info_t *dest = &dest_port->c_dest; + subscribers_t *subs; + struct list_head *p; + int err, src_called = 0; + unsigned long flags; + int exclusive; + + subs = kcalloc(1, sizeof(*subs), GFP_KERNEL); + if (! subs) + return -ENOMEM; + + subs->info = *info; + atomic_set(&subs->ref_count, 2); + + down_write(&src->list_mutex); + down_write(&dest->list_mutex); + + exclusive = info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE ? 1 : 0; + err = -EBUSY; + if (exclusive) { + if (! list_empty(&src->list_head) || ! list_empty(&dest->list_head)) + goto __error; + } else { + if (src->exclusive || dest->exclusive) + goto __error; + /* check whether already exists */ + list_for_each(p, &src->list_head) { + subscribers_t *s = list_entry(p, subscribers_t, src_list); + if (match_subs_info(info, &s->info)) + goto __error; + } + list_for_each(p, &dest->list_head) { + subscribers_t *s = list_entry(p, subscribers_t, dest_list); + if (match_subs_info(info, &s->info)) + goto __error; + } + } + + if ((err = subscribe_port(src_client, src_port, src, info, + connector->number != src_client->number)) < 0) + goto __error; + src_called = 1; + + if ((err = subscribe_port(dest_client, dest_port, dest, info, + connector->number != dest_client->number)) < 0) + goto __error; + + /* add to list */ + write_lock_irqsave(&src->list_lock, flags); + // write_lock(&dest->list_lock); // no other lock yet + list_add_tail(&subs->src_list, &src->list_head); + list_add_tail(&subs->dest_list, &dest->list_head); + // write_unlock(&dest->list_lock); // no other lock yet + write_unlock_irqrestore(&src->list_lock, flags); + + src->exclusive = dest->exclusive = exclusive; + + up_write(&dest->list_mutex); + up_write(&src->list_mutex); + return 0; + + __error: + if (src_called) + unsubscribe_port(src_client, src_port, src, info, + connector->number != src_client->number); + kfree(subs); + up_write(&dest->list_mutex); + up_write(&src->list_mutex); + return err; +} + + +/* remove the connection */ +int snd_seq_port_disconnect(client_t *connector, + client_t *src_client, client_port_t *src_port, + client_t *dest_client, client_port_t *dest_port, + snd_seq_port_subscribe_t *info) +{ + port_subs_info_t *src = &src_port->c_src; + port_subs_info_t *dest = &dest_port->c_dest; + subscribers_t *subs; + struct list_head *p; + int err = -ENOENT; + unsigned long flags; + + down_write(&src->list_mutex); + down_write(&dest->list_mutex); + + /* look for the connection */ + list_for_each(p, &src->list_head) { + subs = list_entry(p, subscribers_t, src_list); + if (match_subs_info(info, &subs->info)) { + write_lock_irqsave(&src->list_lock, flags); + // write_lock(&dest->list_lock); // no lock yet + list_del(&subs->src_list); + list_del(&subs->dest_list); + // write_unlock(&dest->list_lock); + write_unlock_irqrestore(&src->list_lock, flags); + src->exclusive = dest->exclusive = 0; + unsubscribe_port(src_client, src_port, src, info, + connector->number != src_client->number); + unsubscribe_port(dest_client, dest_port, dest, info, + connector->number != dest_client->number); + kfree(subs); + err = 0; + break; + } + } + + up_write(&dest->list_mutex); + up_write(&src->list_mutex); + return err; +} + + +/* get matched subscriber */ +subscribers_t *snd_seq_port_get_subscription(port_subs_info_t *src_grp, + snd_seq_addr_t *dest_addr) +{ + struct list_head *p; + subscribers_t *s, *found = NULL; + + down_read(&src_grp->list_mutex); + list_for_each(p, &src_grp->list_head) { + s = list_entry(p, subscribers_t, src_list); + if (addr_match(dest_addr, &s->info.dest)) { + found = s; + break; + } + } + up_read(&src_grp->list_mutex); + return found; +} + +/* + * Attach a device driver that wants to receive events from the + * sequencer. Returns the new port number on success. + * A driver that wants to receive the events converted to midi, will + * use snd_seq_midisynth_register_port(). + */ +/* exported */ +int snd_seq_event_port_attach(int client, + snd_seq_port_callback_t *pcbp, + int cap, int type, int midi_channels, + int midi_voices, char *portname) +{ + snd_seq_port_info_t portinfo; + int ret; + + /* Set up the port */ + memset(&portinfo, 0, sizeof(portinfo)); + portinfo.addr.client = client; + strlcpy(portinfo.name, portname ? portname : "Unamed port", + sizeof(portinfo.name)); + + portinfo.capability = cap; + portinfo.type = type; + portinfo.kernel = pcbp; + portinfo.midi_channels = midi_channels; + portinfo.midi_voices = midi_voices; + + /* Create it */ + ret = snd_seq_kernel_client_ctl(client, + SNDRV_SEQ_IOCTL_CREATE_PORT, + &portinfo); + + if (ret >= 0) + ret = portinfo.addr.port; + + return ret; +} + + +/* + * Detach the driver from a port. + */ +/* exported */ +int snd_seq_event_port_detach(int client, int port) +{ + snd_seq_port_info_t portinfo; + int err; + + memset(&portinfo, 0, sizeof(portinfo)); + portinfo.addr.client = client; + portinfo.addr.port = port; + err = snd_seq_kernel_client_ctl(client, + SNDRV_SEQ_IOCTL_DELETE_PORT, + &portinfo); + + return err; +} diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h new file mode 100644 index 000000000000..89fd4416f6fa --- /dev/null +++ b/sound/core/seq/seq_ports.h @@ -0,0 +1,128 @@ +/* + * ALSA sequencer Ports + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_PORTS_H +#define __SND_SEQ_PORTS_H + +#include <sound/seq_kernel.h> +#include "seq_lock.h" + +/* list of 'exported' ports */ + +/* Client ports that are not exported are still accessible, but are + anonymous ports. + + If a port supports SUBSCRIPTION, that port can send events to all + subscribersto a special address, with address + (queue==SNDRV_SEQ_ADDRESS_SUBSCRIBERS). The message is then send to all + recipients that are registered in the subscription list. A typical + application for these SUBSCRIPTION events is handling of incoming MIDI + data. The port doesn't 'know' what other clients are interested in this + message. If for instance a MIDI recording application would like to receive + the events from that port, it will first have to subscribe with that port. + +*/ + +typedef struct subscribers_t { + snd_seq_port_subscribe_t info; /* additional info */ + struct list_head src_list; /* link of sources */ + struct list_head dest_list; /* link of destinations */ + atomic_t ref_count; +} subscribers_t; + +typedef struct port_subs_info_t { + struct list_head list_head; /* list of subscribed ports */ + unsigned int count; /* count of subscribers */ + unsigned int exclusive: 1; /* exclusive mode */ + struct rw_semaphore list_mutex; + rwlock_t list_lock; + snd_seq_kernel_port_open_t *open; + snd_seq_kernel_port_close_t *close; +} port_subs_info_t; + +typedef struct client_port_t { + + snd_seq_addr_t addr; /* client/port number */ + struct module *owner; /* owner of this port */ + char name[64]; /* port name */ + struct list_head list; /* port list */ + snd_use_lock_t use_lock; + + /* subscribers */ + port_subs_info_t c_src; /* read (sender) list */ + port_subs_info_t c_dest; /* write (dest) list */ + + snd_seq_kernel_port_input_t *event_input; + snd_seq_kernel_port_private_free_t *private_free; + void *private_data; + unsigned int callback_all : 1; + unsigned int closing : 1; + unsigned int timestamping: 1; + unsigned int time_real: 1; + int time_queue; + + /* capability, inport, output, sync */ + unsigned int capability; /* port capability bits */ + unsigned int type; /* port type bits */ + + /* supported channels */ + int midi_channels; + int midi_voices; + int synth_voices; + +} client_port_t; + +/* return pointer to port structure and lock port */ +client_port_t *snd_seq_port_use_ptr(client_t *client, int num); + +/* search for next port - port is locked if found */ +client_port_t *snd_seq_port_query_nearest(client_t *client, snd_seq_port_info_t *pinfo); + +/* unlock the port */ +#define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock) + +/* create a port, port number is returned (-1 on failure) */ +client_port_t *snd_seq_create_port(client_t *client, int port_index); + +/* delete a port */ +int snd_seq_delete_port(client_t *client, int port); + +/* delete all ports */ +int snd_seq_delete_all_ports(client_t *client); + +/* set port info fields */ +int snd_seq_set_port_info(client_port_t *port, snd_seq_port_info_t *info); + +/* get port info fields */ +int snd_seq_get_port_info(client_port_t *port, snd_seq_port_info_t *info); + +/* add subscriber to subscription list */ +int snd_seq_port_connect(client_t *caller, client_t *s, client_port_t *sp, client_t *d, client_port_t *dp, snd_seq_port_subscribe_t *info); + +/* remove subscriber from subscription list */ +int snd_seq_port_disconnect(client_t *caller, client_t *s, client_port_t *sp, client_t *d, client_port_t *dp, snd_seq_port_subscribe_t *info); + +/* subscribe port */ +int snd_seq_port_subscribe(client_port_t *port, snd_seq_port_subscribe_t *info); + +/* get matched subscriber */ +subscribers_t *snd_seq_port_get_subscription(port_subs_info_t *src_grp, snd_seq_addr_t *dest_addr); + +#endif diff --git a/sound/core/seq/seq_prioq.c b/sound/core/seq/seq_prioq.c new file mode 100644 index 000000000000..a519732ed833 --- /dev/null +++ b/sound/core/seq/seq_prioq.c @@ -0,0 +1,449 @@ +/* + * ALSA sequencer Priority Queue + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/time.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "seq_timer.h" +#include "seq_prioq.h" + + +/* Implementation is a simple linked list for now... + + This priority queue orders the events on timestamp. For events with an + equeal timestamp the queue behaves as a FIFO. + + * + * +-------+ + * Head --> | first | + * +-------+ + * |next + * +-----v-+ + * | | + * +-------+ + * | + * +-----v-+ + * | | + * +-------+ + * | + * +-----v-+ + * Tail --> | last | + * +-------+ + * + + */ + + + +/* create new prioq (constructor) */ +prioq_t *snd_seq_prioq_new(void) +{ + prioq_t *f; + + f = kcalloc(1, sizeof(*f), GFP_KERNEL); + if (f == NULL) { + snd_printd("oops: malloc failed for snd_seq_prioq_new()\n"); + return NULL; + } + + spin_lock_init(&f->lock); + f->head = NULL; + f->tail = NULL; + f->cells = 0; + + return f; +} + +/* delete prioq (destructor) */ +void snd_seq_prioq_delete(prioq_t **fifo) +{ + prioq_t *f = *fifo; + *fifo = NULL; + + if (f == NULL) { + snd_printd("oops: snd_seq_prioq_delete() called with NULL prioq\n"); + return; + } + + /* release resources...*/ + /*....................*/ + + if (f->cells > 0) { + /* drain prioQ */ + while (f->cells > 0) + snd_seq_cell_free(snd_seq_prioq_cell_out(f)); + } + + kfree(f); +} + + + + +/* compare timestamp between events */ +/* return 1 if a >= b; 0 */ +static inline int compare_timestamp(snd_seq_event_t * a, snd_seq_event_t * b) +{ + if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) { + /* compare ticks */ + return (snd_seq_compare_tick_time(&a->time.tick, &b->time.tick)); + } else { + /* compare real time */ + return (snd_seq_compare_real_time(&a->time.time, &b->time.time)); + } +} + +/* compare timestamp between events */ +/* return negative if a < b; + * zero if a = b; + * positive if a > b; + */ +static inline int compare_timestamp_rel(snd_seq_event_t *a, snd_seq_event_t *b) +{ + if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) { + /* compare ticks */ + if (a->time.tick > b->time.tick) + return 1; + else if (a->time.tick == b->time.tick) + return 0; + else + return -1; + } else { + /* compare real time */ + if (a->time.time.tv_sec > b->time.time.tv_sec) + return 1; + else if (a->time.time.tv_sec == b->time.time.tv_sec) { + if (a->time.time.tv_nsec > b->time.time.tv_nsec) + return 1; + else if (a->time.time.tv_nsec == b->time.time.tv_nsec) + return 0; + else + return -1; + } else + return -1; + } +} + +/* enqueue cell to prioq */ +int snd_seq_prioq_cell_in(prioq_t * f, snd_seq_event_cell_t * cell) +{ + snd_seq_event_cell_t *cur, *prev; + unsigned long flags; + int count; + int prior; + + snd_assert(f, return -EINVAL); + snd_assert(cell, return -EINVAL); + + /* check flags */ + prior = (cell->event.flags & SNDRV_SEQ_PRIORITY_MASK); + + spin_lock_irqsave(&f->lock, flags); + + /* check if this element needs to inserted at the end (ie. ordered + data is inserted) This will be very likeley if a sequencer + application or midi file player is feeding us (sequential) data */ + if (f->tail && !prior) { + if (compare_timestamp(&cell->event, &f->tail->event)) { + /* add new cell to tail of the fifo */ + f->tail->next = cell; + f->tail = cell; + cell->next = NULL; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + return 0; + } + } + /* traverse list of elements to find the place where the new cell is + to be inserted... Note that this is a order n process ! */ + + prev = NULL; /* previous cell */ + cur = f->head; /* cursor */ + + count = 10000; /* FIXME: enough big, isn't it? */ + while (cur != NULL) { + /* compare timestamps */ + int rel = compare_timestamp_rel(&cell->event, &cur->event); + if (rel < 0) + /* new cell has earlier schedule time, */ + break; + else if (rel == 0 && prior) + /* equal schedule time and prior to others */ + break; + /* new cell has equal or larger schedule time, */ + /* move cursor to next cell */ + prev = cur; + cur = cur->next; + if (! --count) { + spin_unlock_irqrestore(&f->lock, flags); + snd_printk(KERN_ERR "cannot find a pointer.. infinite loop?\n"); + return -EINVAL; + } + } + + /* insert it before cursor */ + if (prev != NULL) + prev->next = cell; + cell->next = cur; + + if (f->head == cur) /* this is the first cell, set head to it */ + f->head = cell; + if (cur == NULL) /* reached end of the list */ + f->tail = cell; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + return 0; +} + +/* dequeue cell from prioq */ +snd_seq_event_cell_t *snd_seq_prioq_cell_out(prioq_t * f) +{ + snd_seq_event_cell_t *cell; + unsigned long flags; + + if (f == NULL) { + snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n"); + return NULL; + } + spin_lock_irqsave(&f->lock, flags); + + cell = f->head; + if (cell) { + f->head = cell->next; + + /* reset tail if this was the last element */ + if (f->tail == cell) + f->tail = NULL; + + cell->next = NULL; + f->cells--; + } + + spin_unlock_irqrestore(&f->lock, flags); + return cell; +} + +/* return number of events available in prioq */ +int snd_seq_prioq_avail(prioq_t * f) +{ + if (f == NULL) { + snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n"); + return 0; + } + return f->cells; +} + + +/* peek at cell at the head of the prioq */ +snd_seq_event_cell_t *snd_seq_prioq_cell_peek(prioq_t * f) +{ + if (f == NULL) { + snd_printd("oops: snd_seq_prioq_cell_in() called with NULL prioq\n"); + return NULL; + } + return f->head; +} + + +static inline int prioq_match(snd_seq_event_cell_t *cell, int client, int timestamp) +{ + if (cell->event.source.client == client || + cell->event.dest.client == client) + return 1; + if (!timestamp) + return 0; + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + if (cell->event.time.tick) + return 1; + break; + case SNDRV_SEQ_TIME_STAMP_REAL: + if (cell->event.time.time.tv_sec || + cell->event.time.time.tv_nsec) + return 1; + break; + } + return 0; +} + +/* remove cells for left client */ +void snd_seq_prioq_leave(prioq_t * f, int client, int timestamp) +{ + register snd_seq_event_cell_t *cell, *next; + unsigned long flags; + snd_seq_event_cell_t *prev = NULL; + snd_seq_event_cell_t *freefirst = NULL, *freeprev = NULL, *freenext; + + /* collect all removed cells */ + spin_lock_irqsave(&f->lock, flags); + cell = f->head; + while (cell) { + next = cell->next; + if (prioq_match(cell, client, timestamp)) { + /* remove cell from prioq */ + if (cell == f->head) { + f->head = cell->next; + } else { + prev->next = cell->next; + } + if (cell == f->tail) + f->tail = cell->next; + f->cells--; + /* add cell to free list */ + cell->next = NULL; + if (freefirst == NULL) { + freefirst = cell; + } else { + freeprev->next = cell; + } + freeprev = cell; + } else { +#if 0 + printk("type = %i, source = %i, dest = %i, client = %i\n", + cell->event.type, + cell->event.source.client, + cell->event.dest.client, + client); +#endif + prev = cell; + } + cell = next; + } + spin_unlock_irqrestore(&f->lock, flags); + + /* remove selected cells */ + while (freefirst) { + freenext = freefirst->next; + snd_seq_cell_free(freefirst); + freefirst = freenext; + } +} + +static int prioq_remove_match(snd_seq_remove_events_t *info, + snd_seq_event_t *ev) +{ + int res; + + if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) { + if (ev->dest.client != info->dest.client || + ev->dest.port != info->dest.port) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST_CHANNEL) { + if (! snd_seq_ev_is_channel_type(ev)) + return 0; + /* data.note.channel and data.control.channel are identical */ + if (ev->data.note.channel != info->channel) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_AFTER) { + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK) + res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick); + else + res = snd_seq_compare_real_time(&ev->time.time, &info->time.time); + if (!res) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_BEFORE) { + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK) + res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick); + else + res = snd_seq_compare_real_time(&ev->time.time, &info->time.time); + if (res) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_EVENT_TYPE) { + if (ev->type != info->type) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_IGNORE_OFF) { + /* Do not remove off events */ + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEOFF: + /* case SNDRV_SEQ_EVENT_SAMPLE_STOP: */ + return 0; + default: + break; + } + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TAG_MATCH) { + if (info->tag != ev->tag) + return 0; + } + + return 1; +} + +/* remove cells matching remove criteria */ +void snd_seq_prioq_remove_events(prioq_t * f, int client, + snd_seq_remove_events_t *info) +{ + register snd_seq_event_cell_t *cell, *next; + unsigned long flags; + snd_seq_event_cell_t *prev = NULL; + snd_seq_event_cell_t *freefirst = NULL, *freeprev = NULL, *freenext; + + /* collect all removed cells */ + spin_lock_irqsave(&f->lock, flags); + cell = f->head; + + while (cell) { + next = cell->next; + if (cell->event.source.client == client && + prioq_remove_match(info, &cell->event)) { + + /* remove cell from prioq */ + if (cell == f->head) { + f->head = cell->next; + } else { + prev->next = cell->next; + } + + if (cell == f->tail) + f->tail = cell->next; + f->cells--; + + /* add cell to free list */ + cell->next = NULL; + if (freefirst == NULL) { + freefirst = cell; + } else { + freeprev->next = cell; + } + + freeprev = cell; + } else { + prev = cell; + } + cell = next; + } + spin_unlock_irqrestore(&f->lock, flags); + + /* remove selected cells */ + while (freefirst) { + freenext = freefirst->next; + snd_seq_cell_free(freefirst); + freefirst = freenext; + } +} + + diff --git a/sound/core/seq/seq_prioq.h b/sound/core/seq/seq_prioq.h new file mode 100644 index 000000000000..f12af79308b8 --- /dev/null +++ b/sound/core/seq/seq_prioq.h @@ -0,0 +1,62 @@ +/* + * ALSA sequencer Priority Queue + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_PRIOQ_H +#define __SND_SEQ_PRIOQ_H + +#include "seq_memory.h" + + +/* === PRIOQ === */ + +typedef struct { + snd_seq_event_cell_t* head; /* pointer to head of prioq */ + snd_seq_event_cell_t* tail; /* pointer to tail of prioq */ + int cells; + spinlock_t lock; +} prioq_t; + + +/* create new prioq (constructor) */ +extern prioq_t *snd_seq_prioq_new(void); + +/* delete prioq (destructor) */ +extern void snd_seq_prioq_delete(prioq_t **fifo); + +/* enqueue cell to prioq */ +extern int snd_seq_prioq_cell_in(prioq_t *f, snd_seq_event_cell_t *cell); + +/* dequeue cell from prioq */ +extern snd_seq_event_cell_t *snd_seq_prioq_cell_out(prioq_t *f); + +/* return number of events available in prioq */ +extern int snd_seq_prioq_avail(prioq_t *f); + +/* peek at cell at the head of the prioq */ +extern snd_seq_event_cell_t *snd_seq_prioq_cell_peek(prioq_t *f); + +/* client left queue */ +extern void snd_seq_prioq_leave(prioq_t *f, int client, int timestamp); + +/* Remove events */ +void snd_seq_prioq_remove_events(prioq_t * f, int client, + snd_seq_remove_events_t *info); + +#endif diff --git a/sound/core/seq/seq_queue.c b/sound/core/seq/seq_queue.c new file mode 100644 index 000000000000..3afc7cc0c9a7 --- /dev/null +++ b/sound/core/seq/seq_queue.c @@ -0,0 +1,783 @@ +/* + * ALSA sequencer Timing queue handling + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * MAJOR CHANGES + * Nov. 13, 1999 Takashi Iwai <iwai@ww.uni-erlangen.de> + * - Queues are allocated dynamically via ioctl. + * - When owner client is deleted, all owned queues are deleted, too. + * - Owner of unlocked queue is kept unmodified even if it is + * manipulated by other clients. + * - Owner field in SET_QUEUE_OWNER ioctl must be identical with the + * caller client. i.e. Changing owner to a third client is not + * allowed. + * + * Aug. 30, 2000 Takashi Iwai + * - Queues are managed in static array again, but with better way. + * The API itself is identical. + * - The queue is locked when queue_t pinter is returned via + * queueptr(). This pointer *MUST* be released afterward by + * queuefree(ptr). + * - Addition of experimental sync support. + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <sound/core.h> + +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_clientmgr.h" +#include "seq_fifo.h" +#include "seq_timer.h" +#include "seq_info.h" + +/* list of allocated queues */ +static queue_t *queue_list[SNDRV_SEQ_MAX_QUEUES]; +static DEFINE_SPINLOCK(queue_list_lock); +/* number of queues allocated */ +static int num_queues; + +int snd_seq_queue_get_cur_queues(void) +{ + return num_queues; +} + +/*----------------------------------------------------------------*/ + +/* assign queue id and insert to list */ +static int queue_list_add(queue_t *q) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&queue_list_lock, flags); + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if (! queue_list[i]) { + queue_list[i] = q; + q->queue = i; + num_queues++; + spin_unlock_irqrestore(&queue_list_lock, flags); + return i; + } + } + spin_unlock_irqrestore(&queue_list_lock, flags); + return -1; +} + +static queue_t *queue_list_remove(int id, int client) +{ + queue_t *q; + unsigned long flags; + + spin_lock_irqsave(&queue_list_lock, flags); + q = queue_list[id]; + if (q) { + spin_lock(&q->owner_lock); + if (q->owner == client) { + /* found */ + q->klocked = 1; + spin_unlock(&q->owner_lock); + queue_list[id] = NULL; + num_queues--; + spin_unlock_irqrestore(&queue_list_lock, flags); + return q; + } + spin_unlock(&q->owner_lock); + } + spin_unlock_irqrestore(&queue_list_lock, flags); + return NULL; +} + +/*----------------------------------------------------------------*/ + +/* create new queue (constructor) */ +static queue_t *queue_new(int owner, int locked) +{ + queue_t *q; + + q = kcalloc(1, sizeof(*q), GFP_KERNEL); + if (q == NULL) { + snd_printd("malloc failed for snd_seq_queue_new()\n"); + return NULL; + } + + spin_lock_init(&q->owner_lock); + spin_lock_init(&q->check_lock); + init_MUTEX(&q->timer_mutex); + snd_use_lock_init(&q->use_lock); + q->queue = -1; + + q->tickq = snd_seq_prioq_new(); + q->timeq = snd_seq_prioq_new(); + q->timer = snd_seq_timer_new(); + if (q->tickq == NULL || q->timeq == NULL || q->timer == NULL) { + snd_seq_prioq_delete(&q->tickq); + snd_seq_prioq_delete(&q->timeq); + snd_seq_timer_delete(&q->timer); + kfree(q); + return NULL; + } + + q->owner = owner; + q->locked = locked; + q->klocked = 0; + + return q; +} + +/* delete queue (destructor) */ +static void queue_delete(queue_t *q) +{ + /* stop and release the timer */ + snd_seq_timer_stop(q->timer); + snd_seq_timer_close(q); + /* wait until access free */ + snd_use_lock_sync(&q->use_lock); + /* release resources... */ + snd_seq_prioq_delete(&q->tickq); + snd_seq_prioq_delete(&q->timeq); + snd_seq_timer_delete(&q->timer); + + kfree(q); +} + + +/*----------------------------------------------------------------*/ + +/* setup queues */ +int __init snd_seq_queues_init(void) +{ + /* + memset(queue_list, 0, sizeof(queue_list)); + num_queues = 0; + */ + return 0; +} + +/* delete all existing queues */ +void __exit snd_seq_queues_delete(void) +{ + int i; + + /* clear list */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if (queue_list[i]) + queue_delete(queue_list[i]); + } +} + +/* allocate a new queue - + * return queue index value or negative value for error + */ +int snd_seq_queue_alloc(int client, int locked, unsigned int info_flags) +{ + queue_t *q; + + q = queue_new(client, locked); + if (q == NULL) + return -ENOMEM; + q->info_flags = info_flags; + if (queue_list_add(q) < 0) { + queue_delete(q); + return -ENOMEM; + } + snd_seq_queue_use(q->queue, client, 1); /* use this queue */ + return q->queue; +} + +/* delete a queue - queue must be owned by the client */ +int snd_seq_queue_delete(int client, int queueid) +{ + queue_t *q; + + if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES) + return -EINVAL; + q = queue_list_remove(queueid, client); + if (q == NULL) + return -EINVAL; + queue_delete(q); + + return 0; +} + + +/* return pointer to queue structure for specified id */ +queue_t *queueptr(int queueid) +{ + queue_t *q; + unsigned long flags; + + if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES) + return NULL; + spin_lock_irqsave(&queue_list_lock, flags); + q = queue_list[queueid]; + if (q) + snd_use_lock_use(&q->use_lock); + spin_unlock_irqrestore(&queue_list_lock, flags); + return q; +} + +/* return the (first) queue matching with the specified name */ +queue_t *snd_seq_queue_find_name(char *name) +{ + int i; + queue_t *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) != NULL) { + if (strncmp(q->name, name, sizeof(q->name)) == 0) + return q; + queuefree(q); + } + } + return NULL; +} + + +/* -------------------------------------------------------- */ + +void snd_seq_check_queue(queue_t *q, int atomic, int hop) +{ + unsigned long flags; + snd_seq_event_cell_t *cell; + + if (q == NULL) + return; + + /* make this function non-reentrant */ + spin_lock_irqsave(&q->check_lock, flags); + if (q->check_blocked) { + q->check_again = 1; + spin_unlock_irqrestore(&q->check_lock, flags); + return; /* other thread is already checking queues */ + } + q->check_blocked = 1; + spin_unlock_irqrestore(&q->check_lock, flags); + + __again: + /* Process tick queue... */ + while ((cell = snd_seq_prioq_cell_peek(q->tickq)) != NULL) { + if (snd_seq_compare_tick_time(&q->timer->tick.cur_tick, &cell->event.time.tick)) { + cell = snd_seq_prioq_cell_out(q->tickq); + if (cell) + snd_seq_dispatch_event(cell, atomic, hop); + } else { + /* event remains in the queue */ + break; + } + } + + + /* Process time queue... */ + while ((cell = snd_seq_prioq_cell_peek(q->timeq)) != NULL) { + if (snd_seq_compare_real_time(&q->timer->cur_time, &cell->event.time.time)) { + cell = snd_seq_prioq_cell_out(q->timeq); + if (cell) + snd_seq_dispatch_event(cell, atomic, hop); + } else { + /* event remains in the queue */ + break; + } + } + + /* free lock */ + spin_lock_irqsave(&q->check_lock, flags); + if (q->check_again) { + q->check_again = 0; + spin_unlock_irqrestore(&q->check_lock, flags); + goto __again; + } + q->check_blocked = 0; + spin_unlock_irqrestore(&q->check_lock, flags); +} + + +/* enqueue a event to singe queue */ +int snd_seq_enqueue_event(snd_seq_event_cell_t *cell, int atomic, int hop) +{ + int dest, err; + queue_t *q; + + snd_assert(cell != NULL, return -EINVAL); + dest = cell->event.queue; /* destination queue */ + q = queueptr(dest); + if (q == NULL) + return -EINVAL; + /* handle relative time stamps, convert them into absolute */ + if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) { + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + cell->event.time.tick += q->timer->tick.cur_tick; + break; + + case SNDRV_SEQ_TIME_STAMP_REAL: + snd_seq_inc_real_time(&cell->event.time.time, &q->timer->cur_time); + break; + } + cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK; + cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS; + } + /* enqueue event in the real-time or midi queue */ + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + err = snd_seq_prioq_cell_in(q->tickq, cell); + break; + + case SNDRV_SEQ_TIME_STAMP_REAL: + default: + err = snd_seq_prioq_cell_in(q->timeq, cell); + break; + } + + if (err < 0) { + queuefree(q); /* unlock */ + return err; + } + + /* trigger dispatching */ + snd_seq_check_queue(q, atomic, hop); + + queuefree(q); /* unlock */ + + return 0; +} + + +/*----------------------------------------------------------------*/ + +static inline int check_access(queue_t *q, int client) +{ + return (q->owner == client) || (!q->locked && !q->klocked); +} + +/* check if the client has permission to modify queue parameters. + * if it does, lock the queue + */ +static int queue_access_lock(queue_t *q, int client) +{ + unsigned long flags; + int access_ok; + + spin_lock_irqsave(&q->owner_lock, flags); + access_ok = check_access(q, client); + if (access_ok) + q->klocked = 1; + spin_unlock_irqrestore(&q->owner_lock, flags); + return access_ok; +} + +/* unlock the queue */ +static inline void queue_access_unlock(queue_t *q) +{ + unsigned long flags; + + spin_lock_irqsave(&q->owner_lock, flags); + q->klocked = 0; + spin_unlock_irqrestore(&q->owner_lock, flags); +} + +/* exported - only checking permission */ +int snd_seq_queue_check_access(int queueid, int client) +{ + queue_t *q = queueptr(queueid); + int access_ok; + unsigned long flags; + + if (! q) + return 0; + spin_lock_irqsave(&q->owner_lock, flags); + access_ok = check_access(q, client); + spin_unlock_irqrestore(&q->owner_lock, flags); + queuefree(q); + return access_ok; +} + +/*----------------------------------------------------------------*/ + +/* + * change queue's owner and permission + */ +int snd_seq_queue_set_owner(int queueid, int client, int locked) +{ + queue_t *q = queueptr(queueid); + + if (q == NULL) + return -EINVAL; + + if (! queue_access_lock(q, client)) { + queuefree(q); + return -EPERM; + } + + q->locked = locked ? 1 : 0; + q->owner = client; + queue_access_unlock(q); + queuefree(q); + + return 0; +} + + +/*----------------------------------------------------------------*/ + +/* open timer - + * q->use mutex should be down before calling this function to avoid + * confliction with snd_seq_queue_use() + */ +int snd_seq_queue_timer_open(int queueid) +{ + int result = 0; + queue_t *queue; + seq_timer_t *tmr; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + tmr = queue->timer; + if ((result = snd_seq_timer_open(queue)) < 0) { + snd_seq_timer_defaults(tmr); + result = snd_seq_timer_open(queue); + } + queuefree(queue); + return result; +} + +/* close timer - + * q->use mutex should be down before calling this function + */ +int snd_seq_queue_timer_close(int queueid) +{ + queue_t *queue; + seq_timer_t *tmr; + int result = 0; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + tmr = queue->timer; + snd_seq_timer_close(queue); + queuefree(queue); + return result; +} + +/* change queue tempo and ppq */ +int snd_seq_queue_timer_set_tempo(int queueid, int client, snd_seq_queue_tempo_t *info) +{ + queue_t *q = queueptr(queueid); + int result; + + if (q == NULL) + return -EINVAL; + if (! queue_access_lock(q, client)) { + queuefree(q); + return -EPERM; + } + + result = snd_seq_timer_set_tempo(q->timer, info->tempo); + if (result >= 0) + result = snd_seq_timer_set_ppq(q->timer, info->ppq); + if (result >= 0 && info->skew_base > 0) + result = snd_seq_timer_set_skew(q->timer, info->skew_value, info->skew_base); + queue_access_unlock(q); + queuefree(q); + return result; +} + + +/* use or unuse this queue - + * if it is the first client, starts the timer. + * if it is not longer used by any clients, stop the timer. + */ +int snd_seq_queue_use(int queueid, int client, int use) +{ + queue_t *queue; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + down(&queue->timer_mutex); + if (use) { + if (!test_and_set_bit(client, queue->clients_bitmap)) + queue->clients++; + } else { + if (test_and_clear_bit(client, queue->clients_bitmap)) + queue->clients--; + } + if (queue->clients) { + if (use && queue->clients == 1) + snd_seq_timer_defaults(queue->timer); + snd_seq_timer_open(queue); + } else { + snd_seq_timer_close(queue); + } + up(&queue->timer_mutex); + queuefree(queue); + return 0; +} + +/* + * check if queue is used by the client + * return negative value if the queue is invalid. + * return 0 if not used, 1 if used. + */ +int snd_seq_queue_is_used(int queueid, int client) +{ + queue_t *q; + int result; + + q = queueptr(queueid); + if (q == NULL) + return -EINVAL; /* invalid queue */ + result = test_bit(client, q->clients_bitmap) ? 1 : 0; + queuefree(q); + return result; +} + + +/*----------------------------------------------------------------*/ + +/* notification that client has left the system - + * stop the timer on all queues owned by this client + */ +void snd_seq_queue_client_termination(int client) +{ + unsigned long flags; + int i; + queue_t *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) == NULL) + continue; + spin_lock_irqsave(&q->owner_lock, flags); + if (q->owner == client) + q->klocked = 1; + spin_unlock_irqrestore(&q->owner_lock, flags); + if (q->owner == client) { + if (q->timer->running) + snd_seq_timer_stop(q->timer); + snd_seq_timer_reset(q->timer); + } + queuefree(q); + } +} + +/* final stage notification - + * remove cells for no longer exist client (for non-owned queue) + * or delete this queue (for owned queue) + */ +void snd_seq_queue_client_leave(int client) +{ + int i; + queue_t *q; + + /* delete own queues from queue list */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queue_list_remove(i, client)) != NULL) + queue_delete(q); + } + + /* remove cells from existing queues - + * they are not owned by this client + */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) == NULL) + continue; + if (test_bit(client, q->clients_bitmap)) { + snd_seq_prioq_leave(q->tickq, client, 0); + snd_seq_prioq_leave(q->timeq, client, 0); + snd_seq_queue_use(q->queue, client, 0); + } + queuefree(q); + } +} + + + +/*----------------------------------------------------------------*/ + +/* remove cells from all queues */ +void snd_seq_queue_client_leave_cells(int client) +{ + int i; + queue_t *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) == NULL) + continue; + snd_seq_prioq_leave(q->tickq, client, 0); + snd_seq_prioq_leave(q->timeq, client, 0); + queuefree(q); + } +} + +/* remove cells based on flush criteria */ +void snd_seq_queue_remove_cells(int client, snd_seq_remove_events_t *info) +{ + int i; + queue_t *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) == NULL) + continue; + if (test_bit(client, q->clients_bitmap) && + (! (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) || + q->queue == info->queue)) { + snd_seq_prioq_remove_events(q->tickq, client, info); + snd_seq_prioq_remove_events(q->timeq, client, info); + } + queuefree(q); + } +} + +/*----------------------------------------------------------------*/ + +/* + * send events to all subscribed ports + */ +static void queue_broadcast_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop) +{ + snd_seq_event_t sev; + + sev = *ev; + + sev.flags = SNDRV_SEQ_TIME_STAMP_TICK|SNDRV_SEQ_TIME_MODE_ABS; + sev.time.tick = q->timer->tick.cur_tick; + sev.queue = q->queue; + sev.data.queue.queue = q->queue; + + /* broadcast events from Timer port */ + sev.source.client = SNDRV_SEQ_CLIENT_SYSTEM; + sev.source.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + sev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + snd_seq_kernel_client_dispatch(SNDRV_SEQ_CLIENT_SYSTEM, &sev, atomic, hop); +} + +/* + * process a received queue-control event. + * this function is exported for seq_sync.c. + */ +void snd_seq_queue_process_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop) +{ + switch (ev->type) { + case SNDRV_SEQ_EVENT_START: + snd_seq_prioq_leave(q->tickq, ev->source.client, 1); + snd_seq_prioq_leave(q->timeq, ev->source.client, 1); + if (! snd_seq_timer_start(q->timer)) + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_CONTINUE: + if (! snd_seq_timer_continue(q->timer)) + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_STOP: + snd_seq_timer_stop(q->timer); + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_TEMPO: + snd_seq_timer_set_tempo(q->timer, ev->data.queue.param.value); + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_SETPOS_TICK: + if (snd_seq_timer_set_position_tick(q->timer, ev->data.queue.param.time.tick) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + + case SNDRV_SEQ_EVENT_SETPOS_TIME: + if (snd_seq_timer_set_position_time(q->timer, ev->data.queue.param.time.time) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + case SNDRV_SEQ_EVENT_QUEUE_SKEW: + if (snd_seq_timer_set_skew(q->timer, + ev->data.queue.param.skew.value, + ev->data.queue.param.skew.base) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + } +} + + +/* + * Queue control via timer control port: + * this function is exported as a callback of timer port. + */ +int snd_seq_control_queue(snd_seq_event_t *ev, int atomic, int hop) +{ + queue_t *q; + + snd_assert(ev != NULL, return -EINVAL); + q = queueptr(ev->data.queue.queue); + + if (q == NULL) + return -EINVAL; + + if (! queue_access_lock(q, ev->source.client)) { + queuefree(q); + return -EPERM; + } + + snd_seq_queue_process_event(q, ev, atomic, hop); + + queue_access_unlock(q); + queuefree(q); + return 0; +} + + +/*----------------------------------------------------------------*/ + +/* exported to seq_info.c */ +void snd_seq_info_queues_read(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + int i, bpm; + queue_t *q; + seq_timer_t *tmr; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if ((q = queueptr(i)) == NULL) + continue; + + tmr = q->timer; + if (tmr->tempo) + bpm = 60000000 / tmr->tempo; + else + bpm = 0; + + snd_iprintf(buffer, "queue %d: [%s]\n", q->queue, q->name); + snd_iprintf(buffer, "owned by client : %d\n", q->owner); + snd_iprintf(buffer, "lock status : %s\n", q->locked ? "Locked" : "Free"); + snd_iprintf(buffer, "queued time events : %d\n", snd_seq_prioq_avail(q->timeq)); + snd_iprintf(buffer, "queued tick events : %d\n", snd_seq_prioq_avail(q->tickq)); + snd_iprintf(buffer, "timer state : %s\n", tmr->running ? "Running" : "Stopped"); + snd_iprintf(buffer, "timer PPQ : %d\n", tmr->ppq); + snd_iprintf(buffer, "current tempo : %d\n", tmr->tempo); + snd_iprintf(buffer, "current BPM : %d\n", bpm); + snd_iprintf(buffer, "current time : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec); + snd_iprintf(buffer, "current tick : %d\n", tmr->tick.cur_tick); + snd_iprintf(buffer, "\n"); + queuefree(q); + } +} diff --git a/sound/core/seq/seq_queue.h b/sound/core/seq/seq_queue.h new file mode 100644 index 000000000000..b1bf5519fb3b --- /dev/null +++ b/sound/core/seq/seq_queue.h @@ -0,0 +1,140 @@ +/* + * ALSA sequencer Queue handling + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_QUEUE_H +#define __SND_SEQ_QUEUE_H + +#include "seq_memory.h" +#include "seq_prioq.h" +#include "seq_timer.h" +#include "seq_lock.h" +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/bitops.h> + +#define SEQ_QUEUE_NO_OWNER (-1) + +struct _snd_seq_queue { + int queue; /* queue number */ + + char name[64]; /* name of this queue */ + + prioq_t *tickq; /* midi tick event queue */ + prioq_t *timeq; /* real-time event queue */ + + seq_timer_t *timer; /* time keeper for this queue */ + int owner; /* client that 'owns' the timer */ + unsigned int locked:1, /* timer is only accesibble by owner if set */ + klocked:1, /* kernel lock (after START) */ + check_again:1, + check_blocked:1; + + unsigned int flags; /* status flags */ + unsigned int info_flags; /* info for sync */ + + spinlock_t owner_lock; + spinlock_t check_lock; + + /* clients which uses this queue (bitmap) */ + DECLARE_BITMAP(clients_bitmap, SNDRV_SEQ_MAX_CLIENTS); + unsigned int clients; /* users of this queue */ + struct semaphore timer_mutex; + + snd_use_lock_t use_lock; +}; + + +/* get the number of current queues */ +int snd_seq_queue_get_cur_queues(void); + +/* init queues structure */ +int snd_seq_queues_init(void); + +/* delete queues */ +void snd_seq_queues_delete(void); + + +/* create new queue (constructor) */ +int snd_seq_queue_alloc(int client, int locked, unsigned int flags); + +/* delete queue (destructor) */ +int snd_seq_queue_delete(int client, int queueid); + +/* notification that client has left the system */ +void snd_seq_queue_client_termination(int client); + +/* final stage */ +void snd_seq_queue_client_leave(int client); + +/* enqueue a event received from one the clients */ +int snd_seq_enqueue_event(snd_seq_event_cell_t *cell, int atomic, int hop); + +/* Remove events */ +void snd_seq_queue_client_leave_cells(int client); +void snd_seq_queue_remove_cells(int client, snd_seq_remove_events_t *info); + +/* return pointer to queue structure for specified id */ +queue_t *queueptr(int queueid); +/* unlock */ +#define queuefree(q) snd_use_lock_free(&(q)->use_lock) + +/* return the (first) queue matching with the specified name */ +queue_t *snd_seq_queue_find_name(char *name); + +/* check single queue and dispatch events */ +void snd_seq_check_queue(queue_t *q, int atomic, int hop); + +/* access to queue's parameters */ +int snd_seq_queue_check_access(int queueid, int client); +int snd_seq_queue_timer_set_tempo(int queueid, int client, snd_seq_queue_tempo_t *info); +int snd_seq_queue_set_owner(int queueid, int client, int locked); +int snd_seq_queue_set_locked(int queueid, int client, int locked); +int snd_seq_queue_timer_open(int queueid); +int snd_seq_queue_timer_close(int queueid); +int snd_seq_queue_use(int queueid, int client, int use); +int snd_seq_queue_is_used(int queueid, int client); + +int snd_seq_control_queue(snd_seq_event_t *ev, int atomic, int hop); +void snd_seq_queue_process_event(queue_t *q, snd_seq_event_t *ev, int atomic, int hop); + +/* + * 64bit division - for sync stuff.. + */ +#if defined(i386) || defined(i486) + +#define udiv_qrnnd(q, r, n1, n0, d) \ + __asm__ ("divl %4" \ + : "=a" ((u32)(q)), \ + "=d" ((u32)(r)) \ + : "0" ((u32)(n0)), \ + "1" ((u32)(n1)), \ + "rm" ((u32)(d))) + +#define u64_div(x,y,q) do {u32 __tmp; udiv_qrnnd(q, __tmp, (x)>>32, x, y);} while (0) +#define u64_mod(x,y,r) do {u32 __tmp; udiv_qrnnd(__tmp, q, (x)>>32, x, y);} while (0) +#define u64_divmod(x,y,q,r) udiv_qrnnd(q, r, (x)>>32, x, y) + +#else +#define u64_div(x,y,q) ((q) = (u32)((u64)(x) / (u64)(y))) +#define u64_mod(x,y,r) ((r) = (u32)((u64)(x) % (u64)(y))) +#define u64_divmod(x,y,q,r) (u64_div(x,y,q), u64_mod(x,y,r)) +#endif + + +#endif diff --git a/sound/core/seq/seq_system.c b/sound/core/seq/seq_system.c new file mode 100644 index 000000000000..e8f0a6683d50 --- /dev/null +++ b/sound/core/seq/seq_system.c @@ -0,0 +1,190 @@ +/* + * ALSA sequencer System services Client + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <sound/core.h> +#include "seq_system.h" +#include "seq_timer.h" +#include "seq_queue.h" + +/* internal client that provide system services, access to timer etc. */ + +/* + * Port "Timer" + * - send tempo /start/stop etc. events to this port to manipulate the + * queue's timer. The queue address is specified in + * data.queue.queue. + * - this port supports subscription. The received timer events are + * broadcasted to all subscribed clients. The modified tempo + * value is stored on data.queue.value. + * The modifier client/port is not send. + * + * Port "Announce" + * - does not receive message + * - supports supscription. For each client or port attaching to or + * detaching from the system an announcement is send to the subscribed + * clients. + * + * Idea: the subscription mechanism might also work handy for distributing + * synchronisation and timing information. In this case we would ideally have + * a list of subscribers for each type of sync (time, tick), for each timing + * queue. + * + * NOTE: the queue to be started, stopped, etc. must be specified + * in data.queue.addr.queue field. queue is used only for + * scheduling, and no longer referred as affected queue. + * They are used only for timer broadcast (see above). + * -- iwai + */ + + +/* client id of our system client */ +static int sysclient = -1; + +/* port id numbers for this client */ +static int announce_port = -1; + + + +/* fill standard header data, source port & channel are filled in */ +static int setheader(snd_seq_event_t * ev, int client, int port) +{ + if (announce_port < 0) + return -ENODEV; + + memset(ev, 0, sizeof(snd_seq_event_t)); + + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + + ev->source.client = sysclient; + ev->source.port = announce_port; + ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + + /* fill data */ + /*ev->data.addr.queue = SNDRV_SEQ_ADDRESS_UNKNOWN;*/ + ev->data.addr.client = client; + ev->data.addr.port = port; + + return 0; +} + + +/* entry points for broadcasting system events */ +void snd_seq_system_broadcast(int client, int port, int type) +{ + snd_seq_event_t ev; + + if (setheader(&ev, client, port) < 0) + return; + ev.type = type; + snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0); +} + +/* entry points for broadcasting system events */ +int snd_seq_system_notify(int client, int port, snd_seq_event_t *ev) +{ + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + ev->source.client = sysclient; + ev->source.port = announce_port; + ev->dest.client = client; + ev->dest.port = port; + return snd_seq_kernel_client_dispatch(sysclient, ev, 0, 0); +} + +/* call-back handler for timer events */ +static int event_input_timer(snd_seq_event_t * ev, int direct, void *private_data, int atomic, int hop) +{ + return snd_seq_control_queue(ev, atomic, hop); +} + +/* register our internal client */ +int __init snd_seq_system_client_init(void) +{ + + snd_seq_client_callback_t callbacks; + snd_seq_port_callback_t pcallbacks; + snd_seq_client_info_t *inf; + snd_seq_port_info_t *port; + + inf = kcalloc(1, sizeof(*inf), GFP_KERNEL); + port = kcalloc(1, sizeof(*port), GFP_KERNEL); + if (! inf || ! port) { + kfree(inf); + kfree(port); + return -ENOMEM; + } + + memset(&callbacks, 0, sizeof(callbacks)); + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.event_input = event_input_timer; + + /* register client */ + callbacks.allow_input = callbacks.allow_output = 1; + sysclient = snd_seq_create_kernel_client(NULL, 0, &callbacks); + + /* set our name */ + inf->client = 0; + inf->type = KERNEL_CLIENT; + strcpy(inf->name, "System"); + snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, inf); + + /* register timer */ + strcpy(port->name, "Timer"); + port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* accept queue control */ + port->capability |= SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast */ + port->kernel = &pcallbacks; + port->type = 0; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->addr.client = sysclient; + port->addr.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, port); + + /* register announcement port */ + strcpy(port->name, "Announce"); + port->capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */ + port->kernel = NULL; + port->type = 0; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->addr.client = sysclient; + port->addr.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, port); + announce_port = port->addr.port; + + kfree(inf); + kfree(port); + return 0; +} + + +/* unregister our internal client */ +void __exit snd_seq_system_client_done(void) +{ + int oldsysclient = sysclient; + + if (oldsysclient >= 0) { + sysclient = -1; + announce_port = -1; + snd_seq_delete_kernel_client(oldsysclient); + } +} diff --git a/sound/core/seq/seq_system.h b/sound/core/seq/seq_system.h new file mode 100644 index 000000000000..900007255bb4 --- /dev/null +++ b/sound/core/seq/seq_system.h @@ -0,0 +1,46 @@ +/* + * ALSA sequencer System Client + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_SYSTEM_H +#define __SND_SEQ_SYSTEM_H + +#include <sound/seq_kernel.h> + + +/* entry points for broadcasting system events */ +void snd_seq_system_broadcast(int client, int port, int type); + +#define snd_seq_system_client_ev_client_start(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_START) +#define snd_seq_system_client_ev_client_exit(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_EXIT) +#define snd_seq_system_client_ev_client_change(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_CHANGE) +#define snd_seq_system_client_ev_port_start(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_START) +#define snd_seq_system_client_ev_port_exit(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_EXIT) +#define snd_seq_system_client_ev_port_change(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_CHANGE) + +int snd_seq_system_notify(int client, int port, snd_seq_event_t *ev); + +/* register our internal client */ +int snd_seq_system_client_init(void); + +/* unregister our internal client */ +void snd_seq_system_client_done(void); + + +#endif diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c new file mode 100644 index 000000000000..753f1c0863cc --- /dev/null +++ b/sound/core/seq/seq_timer.c @@ -0,0 +1,435 @@ +/* + * ALSA sequencer Timer + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <sound/core.h> +#include <linux/slab.h> +#include "seq_timer.h" +#include "seq_queue.h" +#include "seq_info.h" + +extern int seq_default_timer_class; +extern int seq_default_timer_sclass; +extern int seq_default_timer_card; +extern int seq_default_timer_device; +extern int seq_default_timer_subdevice; +extern int seq_default_timer_resolution; + +#define SKEW_BASE 0x10000 /* 16bit shift */ + +void snd_seq_timer_set_tick_resolution(seq_timer_tick_t *tick, int tempo, int ppq, int nticks) +{ + if (tempo < 1000000) + tick->resolution = (tempo * 1000) / ppq; + else { + /* might overflow.. */ + unsigned int s; + s = tempo % ppq; + s = (s * 1000) / ppq; + tick->resolution = (tempo / ppq) * 1000; + tick->resolution += s; + } + if (tick->resolution <= 0) + tick->resolution = 1; + tick->resolution *= nticks; + snd_seq_timer_update_tick(tick, 0); +} + +/* create new timer (constructor) */ +seq_timer_t *snd_seq_timer_new(void) +{ + seq_timer_t *tmr; + + tmr = kcalloc(1, sizeof(*tmr), GFP_KERNEL); + if (tmr == NULL) { + snd_printd("malloc failed for snd_seq_timer_new() \n"); + return NULL; + } + spin_lock_init(&tmr->lock); + + /* reset setup to defaults */ + snd_seq_timer_defaults(tmr); + + /* reset time */ + snd_seq_timer_reset(tmr); + + return tmr; +} + +/* delete timer (destructor) */ +void snd_seq_timer_delete(seq_timer_t **tmr) +{ + seq_timer_t *t = *tmr; + *tmr = NULL; + + if (t == NULL) { + snd_printd("oops: snd_seq_timer_delete() called with NULL timer\n"); + return; + } + t->running = 0; + + /* reset time */ + snd_seq_timer_stop(t); + snd_seq_timer_reset(t); + + kfree(t); +} + +void snd_seq_timer_defaults(seq_timer_t * tmr) +{ + /* setup defaults */ + tmr->ppq = 96; /* 96 PPQ */ + tmr->tempo = 500000; /* 120 BPM */ + snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1); + tmr->running = 0; + + tmr->type = SNDRV_SEQ_TIMER_ALSA; + tmr->alsa_id.dev_class = seq_default_timer_class; + tmr->alsa_id.dev_sclass = seq_default_timer_sclass; + tmr->alsa_id.card = seq_default_timer_card; + tmr->alsa_id.device = seq_default_timer_device; + tmr->alsa_id.subdevice = seq_default_timer_subdevice; + tmr->preferred_resolution = seq_default_timer_resolution; + + tmr->skew = tmr->skew_base = SKEW_BASE; +} + +void snd_seq_timer_reset(seq_timer_t * tmr) +{ + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + + /* reset time & songposition */ + tmr->cur_time.tv_sec = 0; + tmr->cur_time.tv_nsec = 0; + + tmr->tick.cur_tick = 0; + tmr->tick.fraction = 0; + + spin_unlock_irqrestore(&tmr->lock, flags); +} + + +/* called by timer interrupt routine. the period time since previous invocation is passed */ +static void snd_seq_timer_interrupt(snd_timer_instance_t *timeri, + unsigned long resolution, + unsigned long ticks) +{ + unsigned long flags; + queue_t *q = (queue_t *)timeri->callback_data; + seq_timer_t *tmr; + + if (q == NULL) + return; + tmr = q->timer; + if (tmr == NULL) + return; + if (!tmr->running) + return; + + resolution *= ticks; + if (tmr->skew != tmr->skew_base) { + /* FIXME: assuming skew_base = 0x10000 */ + resolution = (resolution >> 16) * tmr->skew + + (((resolution & 0xffff) * tmr->skew) >> 16); + } + + spin_lock_irqsave(&tmr->lock, flags); + + /* update timer */ + snd_seq_inc_time_nsec(&tmr->cur_time, resolution); + + /* calculate current tick */ + snd_seq_timer_update_tick(&tmr->tick, resolution); + + /* register actual time of this timer update */ + do_gettimeofday(&tmr->last_update); + + spin_unlock_irqrestore(&tmr->lock, flags); + + /* check queues and dispatch events */ + snd_seq_check_queue(q, 1, 0); +} + +/* set current tempo */ +int snd_seq_timer_set_tempo(seq_timer_t * tmr, int tempo) +{ + unsigned long flags; + + snd_assert(tmr, return -EINVAL); + if (tempo <= 0) + return -EINVAL; + spin_lock_irqsave(&tmr->lock, flags); + if ((unsigned int)tempo != tmr->tempo) { + tmr->tempo = tempo; + snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1); + } + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current ppq */ +int snd_seq_timer_set_ppq(seq_timer_t * tmr, int ppq) +{ + unsigned long flags; + + snd_assert(tmr, return -EINVAL); + if (ppq <= 0) + return -EINVAL; + spin_lock_irqsave(&tmr->lock, flags); + if (tmr->running && (ppq != tmr->ppq)) { + /* refuse to change ppq on running timers */ + /* because it will upset the song position (ticks) */ + spin_unlock_irqrestore(&tmr->lock, flags); + snd_printd("seq: cannot change ppq of a running timer\n"); + return -EBUSY; + } + + tmr->ppq = ppq; + snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq, 1); + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current tick position */ +int snd_seq_timer_set_position_tick(seq_timer_t *tmr, snd_seq_tick_time_t position) +{ + unsigned long flags; + + snd_assert(tmr, return -EINVAL); + + spin_lock_irqsave(&tmr->lock, flags); + tmr->tick.cur_tick = position; + tmr->tick.fraction = 0; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current real-time position */ +int snd_seq_timer_set_position_time(seq_timer_t *tmr, snd_seq_real_time_t position) +{ + unsigned long flags; + + snd_assert(tmr, return -EINVAL); + + snd_seq_sanity_real_time(&position); + spin_lock_irqsave(&tmr->lock, flags); + tmr->cur_time = position; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set timer skew */ +int snd_seq_timer_set_skew(seq_timer_t *tmr, unsigned int skew, unsigned int base) +{ + unsigned long flags; + + snd_assert(tmr, return -EINVAL); + + /* FIXME */ + if (base != SKEW_BASE) { + snd_printd("invalid skew base 0x%x\n", base); + return -EINVAL; + } + spin_lock_irqsave(&tmr->lock, flags); + tmr->skew = skew; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +int snd_seq_timer_open(queue_t *q) +{ + snd_timer_instance_t *t; + seq_timer_t *tmr; + char str[32]; + int err; + + tmr = q->timer; + snd_assert(tmr != NULL, return -EINVAL); + if (tmr->timeri) + return -EBUSY; + sprintf(str, "sequencer queue %i", q->queue); + if (tmr->type != SNDRV_SEQ_TIMER_ALSA) /* standard ALSA timer */ + return -EINVAL; + if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) + tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; + err = snd_timer_open(&t, str, &tmr->alsa_id, q->queue); + if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) { + if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL || + tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) { + snd_timer_id_t tid; + memset(&tid, 0, sizeof(tid)); + tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL; + tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; + tid.card = -1; + tid.device = SNDRV_TIMER_GLOBAL_SYSTEM; + err = snd_timer_open(&t, str, &tid, q->queue); + } + if (err < 0) { + snd_printk(KERN_ERR "seq fatal error: cannot create timer (%i)\n", err); + return err; + } + } + t->callback = snd_seq_timer_interrupt; + t->callback_data = q; + t->flags |= SNDRV_TIMER_IFLG_AUTO; + tmr->timeri = t; + return 0; +} + +int snd_seq_timer_close(queue_t *q) +{ + seq_timer_t *tmr; + + tmr = q->timer; + snd_assert(tmr != NULL, return -EINVAL); + if (tmr->timeri) { + snd_timer_stop(tmr->timeri); + snd_timer_close(tmr->timeri); + tmr->timeri = NULL; + } + return 0; +} + +int snd_seq_timer_stop(seq_timer_t * tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (!tmr->running) + return 0; + tmr->running = 0; + snd_timer_pause(tmr->timeri); + return 0; +} + +static int initialize_timer(seq_timer_t *tmr) +{ + snd_timer_t *t; + t = tmr->timeri->timer; + snd_assert(t, return -EINVAL); + + tmr->ticks = 1; + if (tmr->preferred_resolution && + ! (t->hw.flags & SNDRV_TIMER_HW_SLAVE)) { + unsigned long r = t->hw.resolution; + if (! r && t->hw.c_resolution) + r = t->hw.c_resolution(t); + if (r) { + tmr->ticks = (unsigned int)(1000000000uL / (r * tmr->preferred_resolution)); + if (! tmr->ticks) + tmr->ticks = 1; + } + } + tmr->initialized = 1; + return 0; +} + +int snd_seq_timer_start(seq_timer_t * tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (tmr->running) + snd_seq_timer_stop(tmr); + snd_seq_timer_reset(tmr); + if (initialize_timer(tmr) < 0) + return -EINVAL; + snd_timer_start(tmr->timeri, tmr->ticks); + tmr->running = 1; + do_gettimeofday(&tmr->last_update); + return 0; +} + +int snd_seq_timer_continue(seq_timer_t * tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (tmr->running) + return -EBUSY; + if (! tmr->initialized) { + snd_seq_timer_reset(tmr); + if (initialize_timer(tmr) < 0) + return -EINVAL; + } + snd_timer_start(tmr->timeri, tmr->ticks); + tmr->running = 1; + do_gettimeofday(&tmr->last_update); + return 0; +} + +/* return current 'real' time. use timeofday() to get better granularity. */ +snd_seq_real_time_t snd_seq_timer_get_cur_time(seq_timer_t *tmr) +{ + snd_seq_real_time_t cur_time; + + cur_time = tmr->cur_time; + if (tmr->running) { + struct timeval tm; + int usec; + do_gettimeofday(&tm); + usec = (int)(tm.tv_usec - tmr->last_update.tv_usec); + if (usec < 0) { + cur_time.tv_nsec += (1000000 + usec) * 1000; + cur_time.tv_sec += tm.tv_sec - tmr->last_update.tv_sec - 1; + } else { + cur_time.tv_nsec += usec * 1000; + cur_time.tv_sec += tm.tv_sec - tmr->last_update.tv_sec; + } + snd_seq_sanity_real_time(&cur_time); + } + + return cur_time; +} + +/* TODO: use interpolation on tick queue (will only be useful for very + high PPQ values) */ +snd_seq_tick_time_t snd_seq_timer_get_cur_tick(seq_timer_t *tmr) +{ + return tmr->tick.cur_tick; +} + + +/* exported to seq_info.c */ +void snd_seq_info_timer_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + int idx; + queue_t *q; + seq_timer_t *tmr; + snd_timer_instance_t *ti; + unsigned long resolution; + + for (idx = 0; idx < SNDRV_SEQ_MAX_QUEUES; idx++) { + q = queueptr(idx); + if (q == NULL) + continue; + if ((tmr = q->timer) == NULL || + (ti = tmr->timeri) == NULL) { + queuefree(q); + continue; + } + snd_iprintf(buffer, "Timer for queue %i : %s\n", q->queue, ti->timer->name); + resolution = snd_timer_resolution(ti) * tmr->ticks; + snd_iprintf(buffer, " Period time : %lu.%09lu\n", resolution / 1000000000, resolution % 1000000000); + snd_iprintf(buffer, " Skew : %u / %u\n", tmr->skew, tmr->skew_base); + queuefree(q); + } +} diff --git a/sound/core/seq/seq_timer.h b/sound/core/seq/seq_timer.h new file mode 100644 index 000000000000..4c0872df8931 --- /dev/null +++ b/sound/core/seq/seq_timer.h @@ -0,0 +1,141 @@ +/* + * ALSA sequencer Timer + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __SND_SEQ_TIMER_H +#define __SND_SEQ_TIMER_H + +#include <sound/timer.h> +#include <sound/seq_kernel.h> + +typedef struct { + snd_seq_tick_time_t cur_tick; /* current tick */ + unsigned long resolution; /* time per tick in nsec */ + unsigned long fraction; /* current time per tick in nsec */ +} seq_timer_tick_t; + +typedef struct { + /* ... tempo / offset / running state */ + + unsigned int running:1, /* running state of queue */ + initialized:1; /* timer is initialized */ + + unsigned int tempo; /* current tempo, us/tick */ + int ppq; /* time resolution, ticks/quarter */ + + snd_seq_real_time_t cur_time; /* current time */ + seq_timer_tick_t tick; /* current tick */ + int tick_updated; + + int type; /* timer type */ + snd_timer_id_t alsa_id; /* ALSA's timer ID */ + snd_timer_instance_t *timeri; /* timer instance */ + unsigned int ticks; + unsigned long preferred_resolution; /* timer resolution, ticks/sec */ + + unsigned int skew; + unsigned int skew_base; + + struct timeval last_update; /* time of last clock update, used for interpolation */ + + spinlock_t lock; +} seq_timer_t; + + +/* create new timer (constructor) */ +extern seq_timer_t *snd_seq_timer_new(void); + +/* delete timer (destructor) */ +extern void snd_seq_timer_delete(seq_timer_t **tmr); + +void snd_seq_timer_set_tick_resolution(seq_timer_tick_t *tick, int tempo, int ppq, int nticks); + +/* */ +static inline void snd_seq_timer_update_tick(seq_timer_tick_t *tick, unsigned long resolution) +{ + if (tick->resolution > 0) { + tick->fraction += resolution; + tick->cur_tick += (unsigned int)(tick->fraction / tick->resolution); + tick->fraction %= tick->resolution; + } +} + + +/* compare timestamp between events */ +/* return 1 if a >= b; otherwise return 0 */ +static inline int snd_seq_compare_tick_time(snd_seq_tick_time_t *a, snd_seq_tick_time_t *b) +{ + /* compare ticks */ + return (*a >= *b); +} + +static inline int snd_seq_compare_real_time(snd_seq_real_time_t *a, snd_seq_real_time_t *b) +{ + /* compare real time */ + if (a->tv_sec > b->tv_sec) + return 1; + if ((a->tv_sec == b->tv_sec) && (a->tv_nsec >= b->tv_nsec)) + return 1; + return 0; +} + + +static inline void snd_seq_sanity_real_time(snd_seq_real_time_t *tm) +{ + while (tm->tv_nsec >= 1000000000) { + /* roll-over */ + tm->tv_nsec -= 1000000000; + tm->tv_sec++; + } +} + + +/* increment timestamp */ +static inline void snd_seq_inc_real_time(snd_seq_real_time_t *tm, snd_seq_real_time_t *inc) +{ + tm->tv_sec += inc->tv_sec; + tm->tv_nsec += inc->tv_nsec; + snd_seq_sanity_real_time(tm); +} + +static inline void snd_seq_inc_time_nsec(snd_seq_real_time_t *tm, unsigned long nsec) +{ + tm->tv_nsec += nsec; + snd_seq_sanity_real_time(tm); +} + +/* called by timer isr */ +int snd_seq_timer_open(queue_t *q); +int snd_seq_timer_close(queue_t *q); +int snd_seq_timer_midi_open(queue_t *q); +int snd_seq_timer_midi_close(queue_t *q); +void snd_seq_timer_defaults(seq_timer_t *tmr); +void snd_seq_timer_reset(seq_timer_t *tmr); +int snd_seq_timer_stop(seq_timer_t *tmr); +int snd_seq_timer_start(seq_timer_t *tmr); +int snd_seq_timer_continue(seq_timer_t *tmr); +int snd_seq_timer_set_tempo(seq_timer_t *tmr, int tempo); +int snd_seq_timer_set_ppq(seq_timer_t *tmr, int ppq); +int snd_seq_timer_set_position_tick(seq_timer_t *tmr, snd_seq_tick_time_t position); +int snd_seq_timer_set_position_time(seq_timer_t *tmr, snd_seq_real_time_t position); +int snd_seq_timer_set_skew(seq_timer_t *tmr, unsigned int skew, unsigned int base); +snd_seq_real_time_t snd_seq_timer_get_cur_time(seq_timer_t *tmr); +snd_seq_tick_time_t snd_seq_timer_get_cur_tick(seq_timer_t *tmr); + +#endif diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c new file mode 100644 index 000000000000..6b4e630ace54 --- /dev/null +++ b/sound/core/seq/seq_virmidi.c @@ -0,0 +1,551 @@ +/* + * Virtual Raw MIDI client on Sequencer + * + * Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>, + * Jaroslav Kysela <perex@perex.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Virtual Raw MIDI client + * + * The virtual rawmidi client is a sequencer client which associate + * a rawmidi device file. The created rawmidi device file can be + * accessed as a normal raw midi, but its MIDI source and destination + * are arbitrary. For example, a user-client software synth connected + * to this port can be used as a normal midi device as well. + * + * The virtual rawmidi device accepts also multiple opens. Each file + * has its own input buffer, so that no conflict would occur. The drain + * of input/output buffer acts only to the local buffer. + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/rawmidi.h> +#include <sound/info.h> +#include <sound/control.h> +#include <sound/minors.h> +#include <sound/seq_kernel.h> +#include <sound/seq_midi_event.h> +#include <sound/seq_virmidi.h> + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("Virtual Raw MIDI client on Sequencer"); +MODULE_LICENSE("GPL"); + +/* + * initialize an event record + */ +static void snd_virmidi_init_event(snd_virmidi_t *vmidi, snd_seq_event_t *ev) +{ + memset(ev, 0, sizeof(*ev)); + ev->source.port = vmidi->port; + switch (vmidi->seq_mode) { + case SNDRV_VIRMIDI_SEQ_DISPATCH: + ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + break; + case SNDRV_VIRMIDI_SEQ_ATTACH: + /* FIXME: source and destination are same - not good.. */ + ev->dest.client = vmidi->client; + ev->dest.port = vmidi->port; + break; + } + ev->type = SNDRV_SEQ_EVENT_NONE; +} + +/* + * decode input event and put to read buffer of each opened file + */ +static int snd_virmidi_dev_receive_event(snd_virmidi_dev_t *rdev, snd_seq_event_t *ev) +{ + snd_virmidi_t *vmidi; + struct list_head *list; + unsigned char msg[4]; + int len; + + read_lock(&rdev->filelist_lock); + list_for_each(list, &rdev->filelist) { + vmidi = list_entry(list, snd_virmidi_t, list); + if (!vmidi->trigger) + continue; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + continue; + snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)snd_rawmidi_receive, vmidi->substream); + } else { + len = snd_midi_event_decode(vmidi->parser, msg, sizeof(msg), ev); + if (len > 0) + snd_rawmidi_receive(vmidi->substream, msg, len); + } + } + read_unlock(&rdev->filelist_lock); + + return 0; +} + +/* + * receive an event from the remote virmidi port + * + * for rawmidi inputs, you can call this function from the event + * handler of a remote port which is attached to the virmidi via + * SNDRV_VIRMIDI_SEQ_ATTACH. + */ +/* exported */ +int snd_virmidi_receive(snd_rawmidi_t *rmidi, snd_seq_event_t *ev) +{ + snd_virmidi_dev_t *rdev; + + rdev = rmidi->private_data; + return snd_virmidi_dev_receive_event(rdev, ev); +} + +/* + * event handler of virmidi port + */ +static int snd_virmidi_event_input(snd_seq_event_t *ev, int direct, + void *private_data, int atomic, int hop) +{ + snd_virmidi_dev_t *rdev; + + rdev = private_data; + if (!(rdev->flags & SNDRV_VIRMIDI_USE)) + return 0; /* ignored */ + return snd_virmidi_dev_receive_event(rdev, ev); +} + +/* + * trigger rawmidi stream for input + */ +static void snd_virmidi_input_trigger(snd_rawmidi_substream_t * substream, int up) +{ + snd_virmidi_t *vmidi = substream->runtime->private_data; + + if (up) { + vmidi->trigger = 1; + } else { + vmidi->trigger = 0; + } +} + +/* + * trigger rawmidi stream for output + */ +static void snd_virmidi_output_trigger(snd_rawmidi_substream_t * substream, int up) +{ + snd_virmidi_t *vmidi = substream->runtime->private_data; + int count, res; + unsigned char buf[32], *pbuf; + + if (up) { + vmidi->trigger = 1; + if (vmidi->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH && + !(vmidi->rdev->flags & SNDRV_VIRMIDI_SUBSCRIBE)) { + snd_rawmidi_transmit_ack(substream, substream->runtime->buffer_size - substream->runtime->avail); + return; /* ignored */ + } + if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) { + if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, 0, 0) < 0) + return; + vmidi->event.type = SNDRV_SEQ_EVENT_NONE; + } + while (1) { + count = snd_rawmidi_transmit_peek(substream, buf, sizeof(buf)); + if (count <= 0) + break; + pbuf = buf; + while (count > 0) { + res = snd_midi_event_encode(vmidi->parser, pbuf, count, &vmidi->event); + if (res < 0) { + snd_midi_event_reset_encode(vmidi->parser); + continue; + } + snd_rawmidi_transmit_ack(substream, res); + pbuf += res; + count -= res; + if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) { + if (snd_seq_kernel_client_dispatch(vmidi->client, &vmidi->event, 0, 0) < 0) + return; + vmidi->event.type = SNDRV_SEQ_EVENT_NONE; + } + } + } + } else { + vmidi->trigger = 0; + } +} + +/* + * open rawmidi handle for input + */ +static int snd_virmidi_input_open(snd_rawmidi_substream_t * substream) +{ + snd_virmidi_dev_t *rdev = substream->rmidi->private_data; + snd_rawmidi_runtime_t *runtime = substream->runtime; + snd_virmidi_t *vmidi; + unsigned long flags; + + vmidi = kcalloc(1, sizeof(*vmidi), GFP_KERNEL); + if (vmidi == NULL) + return -ENOMEM; + vmidi->substream = substream; + if (snd_midi_event_new(0, &vmidi->parser) < 0) { + kfree(vmidi); + return -ENOMEM; + } + vmidi->seq_mode = rdev->seq_mode; + vmidi->client = rdev->client; + vmidi->port = rdev->port; + runtime->private_data = vmidi; + write_lock_irqsave(&rdev->filelist_lock, flags); + list_add_tail(&vmidi->list, &rdev->filelist); + write_unlock_irqrestore(&rdev->filelist_lock, flags); + vmidi->rdev = rdev; + return 0; +} + +/* + * open rawmidi handle for output + */ +static int snd_virmidi_output_open(snd_rawmidi_substream_t * substream) +{ + snd_virmidi_dev_t *rdev = substream->rmidi->private_data; + snd_rawmidi_runtime_t *runtime = substream->runtime; + snd_virmidi_t *vmidi; + + vmidi = kcalloc(1, sizeof(*vmidi), GFP_KERNEL); + if (vmidi == NULL) + return -ENOMEM; + vmidi->substream = substream; + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &vmidi->parser) < 0) { + kfree(vmidi); + return -ENOMEM; + } + vmidi->seq_mode = rdev->seq_mode; + vmidi->client = rdev->client; + vmidi->port = rdev->port; + snd_virmidi_init_event(vmidi, &vmidi->event); + vmidi->rdev = rdev; + runtime->private_data = vmidi; + return 0; +} + +/* + * close rawmidi handle for input + */ +static int snd_virmidi_input_close(snd_rawmidi_substream_t * substream) +{ + snd_virmidi_t *vmidi = substream->runtime->private_data; + snd_midi_event_free(vmidi->parser); + list_del(&vmidi->list); + substream->runtime->private_data = NULL; + kfree(vmidi); + return 0; +} + +/* + * close rawmidi handle for output + */ +static int snd_virmidi_output_close(snd_rawmidi_substream_t * substream) +{ + snd_virmidi_t *vmidi = substream->runtime->private_data; + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); + return 0; +} + +/* + * subscribe callback - allow output to rawmidi device + */ +static int snd_virmidi_subscribe(void *private_data, snd_seq_port_subscribe_t *info) +{ + snd_virmidi_dev_t *rdev; + + rdev = private_data; + if (!try_module_get(rdev->card->module)) + return -EFAULT; + rdev->flags |= SNDRV_VIRMIDI_SUBSCRIBE; + return 0; +} + +/* + * unsubscribe callback - disallow output to rawmidi device + */ +static int snd_virmidi_unsubscribe(void *private_data, snd_seq_port_subscribe_t *info) +{ + snd_virmidi_dev_t *rdev; + + rdev = private_data; + rdev->flags &= ~SNDRV_VIRMIDI_SUBSCRIBE; + module_put(rdev->card->module); + return 0; +} + + +/* + * use callback - allow input to rawmidi device + */ +static int snd_virmidi_use(void *private_data, snd_seq_port_subscribe_t *info) +{ + snd_virmidi_dev_t *rdev; + + rdev = private_data; + if (!try_module_get(rdev->card->module)) + return -EFAULT; + rdev->flags |= SNDRV_VIRMIDI_USE; + return 0; +} + +/* + * unuse callback - disallow input to rawmidi device + */ +static int snd_virmidi_unuse(void *private_data, snd_seq_port_subscribe_t *info) +{ + snd_virmidi_dev_t *rdev; + + rdev = private_data; + rdev->flags &= ~SNDRV_VIRMIDI_USE; + module_put(rdev->card->module); + return 0; +} + + +/* + * Register functions + */ + +static snd_rawmidi_ops_t snd_virmidi_input_ops = { + .open = snd_virmidi_input_open, + .close = snd_virmidi_input_close, + .trigger = snd_virmidi_input_trigger, +}; + +static snd_rawmidi_ops_t snd_virmidi_output_ops = { + .open = snd_virmidi_output_open, + .close = snd_virmidi_output_close, + .trigger = snd_virmidi_output_trigger, +}; + +/* + * create a sequencer client and a port + */ +static int snd_virmidi_dev_attach_seq(snd_virmidi_dev_t *rdev) +{ + int client; + snd_seq_client_callback_t callbacks; + snd_seq_port_callback_t pcallbacks; + snd_seq_client_info_t *info; + snd_seq_port_info_t *pinfo; + int err; + + if (rdev->client >= 0) + return 0; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + pinfo = kmalloc(sizeof(*pinfo), GFP_KERNEL); + if (! info || ! pinfo) { + err = -ENOMEM; + goto __error; + } + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.private_data = rdev; + callbacks.allow_input = 1; + callbacks.allow_output = 1; + client = snd_seq_create_kernel_client(rdev->card, rdev->device, &callbacks); + if (client < 0) { + err = client; + goto __error; + } + rdev->client = client; + + /* set client name */ + memset(info, 0, sizeof(*info)); + info->client = client; + info->type = KERNEL_CLIENT; + sprintf(info->name, "%s %d-%d", rdev->rmidi->name, rdev->card->number, rdev->device); + snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &info); + + /* create a port */ + memset(pinfo, 0, sizeof(*pinfo)); + pinfo->addr.client = client; + sprintf(pinfo->name, "VirMIDI %d-%d", rdev->card->number, rdev->device); + /* set all capabilities */ + pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC; + pinfo->midi_channels = 16; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = rdev; + pcallbacks.subscribe = snd_virmidi_subscribe; + pcallbacks.unsubscribe = snd_virmidi_unsubscribe; + pcallbacks.use = snd_virmidi_use; + pcallbacks.unuse = snd_virmidi_unuse; + pcallbacks.event_input = snd_virmidi_event_input; + pinfo->kernel = &pcallbacks; + err = snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo); + if (err < 0) { + snd_seq_delete_kernel_client(client); + rdev->client = -1; + goto __error; + } + + rdev->port = pinfo->addr.port; + err = 0; /* success */ + + __error: + kfree(info); + kfree(pinfo); + return err; +} + + +/* + * release the sequencer client + */ +static void snd_virmidi_dev_detach_seq(snd_virmidi_dev_t *rdev) +{ + if (rdev->client >= 0) { + snd_seq_delete_kernel_client(rdev->client); + rdev->client = -1; + } +} + +/* + * register the device + */ +static int snd_virmidi_dev_register(snd_rawmidi_t *rmidi) +{ + snd_virmidi_dev_t *rdev = rmidi->private_data; + int err; + + switch (rdev->seq_mode) { + case SNDRV_VIRMIDI_SEQ_DISPATCH: + err = snd_virmidi_dev_attach_seq(rdev); + if (err < 0) + return err; + break; + case SNDRV_VIRMIDI_SEQ_ATTACH: + if (rdev->client == 0) + return -EINVAL; + /* should check presence of port more strictly.. */ + break; + default: + snd_printk(KERN_ERR "seq_mode is not set: %d\n", rdev->seq_mode); + return -EINVAL; + } + return 0; +} + + +/* + * unregister the device + */ +static int snd_virmidi_dev_unregister(snd_rawmidi_t *rmidi) +{ + snd_virmidi_dev_t *rdev = rmidi->private_data; + + if (rdev->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH) + snd_virmidi_dev_detach_seq(rdev); + return 0; +} + +/* + * + */ +static snd_rawmidi_global_ops_t snd_virmidi_global_ops = { + .dev_register = snd_virmidi_dev_register, + .dev_unregister = snd_virmidi_dev_unregister, +}; + +/* + * free device + */ +static void snd_virmidi_free(snd_rawmidi_t *rmidi) +{ + snd_virmidi_dev_t *rdev = rmidi->private_data; + kfree(rdev); +} + +/* + * create a new device + * + */ +/* exported */ +int snd_virmidi_new(snd_card_t *card, int device, snd_rawmidi_t **rrmidi) +{ + snd_rawmidi_t *rmidi; + snd_virmidi_dev_t *rdev; + int err; + + *rrmidi = NULL; + if ((err = snd_rawmidi_new(card, "VirMidi", device, + 16, /* may be configurable */ + 16, /* may be configurable */ + &rmidi)) < 0) + return err; + strcpy(rmidi->name, rmidi->id); + rdev = kcalloc(1, sizeof(*rdev), GFP_KERNEL); + if (rdev == NULL) { + snd_device_free(card, rmidi); + return -ENOMEM; + } + rdev->card = card; + rdev->rmidi = rmidi; + rdev->device = device; + rdev->client = -1; + rwlock_init(&rdev->filelist_lock); + INIT_LIST_HEAD(&rdev->filelist); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; + rmidi->private_data = rdev; + rmidi->private_free = snd_virmidi_free; + rmidi->ops = &snd_virmidi_global_ops; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_virmidi_input_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_virmidi_output_ops); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + *rrmidi = rmidi; + return 0; +} + +/* + * ENTRY functions + */ + +static int __init alsa_virmidi_init(void) +{ + return 0; +} + +static void __exit alsa_virmidi_exit(void) +{ +} + +module_init(alsa_virmidi_init) +module_exit(alsa_virmidi_exit) + +EXPORT_SYMBOL(snd_virmidi_new); +EXPORT_SYMBOL(snd_virmidi_receive); diff --git a/sound/core/sgbuf.c b/sound/core/sgbuf.c new file mode 100644 index 000000000000..74745da9deb6 --- /dev/null +++ b/sound/core/sgbuf.c @@ -0,0 +1,111 @@ +/* + * Scatter-Gather buffer + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/config.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <sound/memalloc.h> + + +/* table entries are align to 32 */ +#define SGBUF_TBL_ALIGN 32 +#define sgbuf_align_table(tbl) ((((tbl) + SGBUF_TBL_ALIGN - 1) / SGBUF_TBL_ALIGN) * SGBUF_TBL_ALIGN) + +int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab) +{ + struct snd_sg_buf *sgbuf = dmab->private_data; + struct snd_dma_buffer tmpb; + int i; + + if (! sgbuf) + return -EINVAL; + + tmpb.dev.type = SNDRV_DMA_TYPE_DEV; + tmpb.dev.dev = sgbuf->dev; + for (i = 0; i < sgbuf->pages; i++) { + tmpb.area = sgbuf->table[i].buf; + tmpb.addr = sgbuf->table[i].addr; + tmpb.bytes = PAGE_SIZE; + snd_dma_free_pages(&tmpb); + } + if (dmab->area) + vunmap(dmab->area); + dmab->area = NULL; + + kfree(sgbuf->table); + kfree(sgbuf->page_table); + kfree(sgbuf); + dmab->private_data = NULL; + + return 0; +} + +void *snd_malloc_sgbuf_pages(struct device *device, + size_t size, struct snd_dma_buffer *dmab, + size_t *res_size) +{ + struct snd_sg_buf *sgbuf; + unsigned int i, pages; + struct snd_dma_buffer tmpb; + + dmab->area = NULL; + dmab->addr = 0; + dmab->private_data = sgbuf = kmalloc(sizeof(*sgbuf), GFP_KERNEL); + if (! sgbuf) + return NULL; + memset(sgbuf, 0, sizeof(*sgbuf)); + sgbuf->dev = device; + pages = snd_sgbuf_aligned_pages(size); + sgbuf->tblsize = sgbuf_align_table(pages); + sgbuf->table = kmalloc(sizeof(*sgbuf->table) * sgbuf->tblsize, GFP_KERNEL); + if (! sgbuf->table) + goto _failed; + memset(sgbuf->table, 0, sizeof(*sgbuf->table) * sgbuf->tblsize); + sgbuf->page_table = kmalloc(sizeof(*sgbuf->page_table) * sgbuf->tblsize, GFP_KERNEL); + if (! sgbuf->page_table) + goto _failed; + memset(sgbuf->page_table, 0, sizeof(*sgbuf->page_table) * sgbuf->tblsize); + + /* allocate each page */ + for (i = 0; i < pages; i++) { + if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, device, PAGE_SIZE, &tmpb) < 0) { + if (res_size == NULL) + goto _failed; + *res_size = size = sgbuf->pages * PAGE_SIZE; + break; + } + sgbuf->table[i].buf = tmpb.area; + sgbuf->table[i].addr = tmpb.addr; + sgbuf->page_table[i] = virt_to_page(tmpb.area); + sgbuf->pages++; + } + + sgbuf->size = size; + dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL); + if (! dmab->area) + goto _failed; + return dmab->area; + + _failed: + snd_free_sgbuf_pages(dmab); /* free the table */ + return NULL; +} diff --git a/sound/core/sound.c b/sound/core/sound.c new file mode 100644 index 000000000000..88e052079f85 --- /dev/null +++ b/sound/core/sound.c @@ -0,0 +1,491 @@ +/* + * Advanced Linux Sound Architecture + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <sound/version.h> +#include <sound/control.h> +#include <sound/initval.h> +#include <linux/kmod.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/device.h> + +#define SNDRV_OS_MINORS 256 + +static int major = CONFIG_SND_MAJOR; +int snd_major; +static int cards_limit = 1; +static int device_mode = S_IFCHR | S_IRUGO | S_IWUGO; + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture driver for soundcards."); +MODULE_LICENSE("GPL"); +module_param(major, int, 0444); +MODULE_PARM_DESC(major, "Major # for sound driver."); +module_param(cards_limit, int, 0444); +MODULE_PARM_DESC(cards_limit, "Count of auto-loadable soundcards."); +#ifdef CONFIG_DEVFS_FS +module_param(device_mode, int, 0444); +MODULE_PARM_DESC(device_mode, "Device file permission mask for devfs."); +#endif +MODULE_ALIAS_CHARDEV_MAJOR(CONFIG_SND_MAJOR); + +/* this one holds the actual max. card number currently available. + * as default, it's identical with cards_limit option. when more + * modules are loaded manually, this limit number increases, too. + */ +int snd_ecards_limit; + +static struct list_head snd_minors_hash[SNDRV_CARDS]; + +static DECLARE_MUTEX(sound_mutex); + +extern struct class_simple *sound_class; + + +#ifdef CONFIG_KMOD + +/** + * snd_request_card - try to load the card module + * @card: the card number + * + * Tries to load the module "snd-card-X" for the given card number + * via KMOD. Returns immediately if already loaded. + */ +void snd_request_card(int card) +{ + int locked; + + if (! current->fs->root) + return; + read_lock(&snd_card_rwlock); + locked = snd_cards_lock & (1 << card); + read_unlock(&snd_card_rwlock); + if (locked) + return; + if (card < 0 || card >= cards_limit) + return; + request_module("snd-card-%i", card); +} + +static void snd_request_other(int minor) +{ + char *str; + + if (! current->fs->root) + return; + switch (minor) { + case SNDRV_MINOR_SEQUENCER: str = "snd-seq"; break; + case SNDRV_MINOR_TIMER: str = "snd-timer"; break; + default: return; + } + request_module(str); +} + +#endif /* request_module support */ + +static snd_minor_t *snd_minor_search(int minor) +{ + struct list_head *list; + snd_minor_t *mptr; + + list_for_each(list, &snd_minors_hash[SNDRV_MINOR_CARD(minor)]) { + mptr = list_entry(list, snd_minor_t, list); + if (mptr->number == minor) + return mptr; + } + return NULL; +} + +static int snd_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + int card = SNDRV_MINOR_CARD(minor); + int dev = SNDRV_MINOR_DEVICE(minor); + snd_minor_t *mptr = NULL; + struct file_operations *old_fops; + int err = 0; + + if (dev != SNDRV_MINOR_SEQUENCER && dev != SNDRV_MINOR_TIMER) { + if (snd_cards[card] == NULL) { +#ifdef CONFIG_KMOD + snd_request_card(card); + if (snd_cards[card] == NULL) +#endif + return -ENODEV; + } + } else { +#ifdef CONFIG_KMOD + if ((mptr = snd_minor_search(minor)) == NULL) + snd_request_other(minor); +#endif + } + if (mptr == NULL && (mptr = snd_minor_search(minor)) == NULL) + return -ENODEV; + old_fops = file->f_op; + file->f_op = fops_get(mptr->f_ops); + if (file->f_op->open) + err = file->f_op->open(inode, file); + if (err) { + fops_put(file->f_op); + file->f_op = fops_get(old_fops); + } + fops_put(old_fops); + return err; +} + +static struct file_operations snd_fops = +{ + .owner = THIS_MODULE, + .open = snd_open +}; + +static int snd_kernel_minor(int type, snd_card_t * card, int dev) +{ + int minor; + + switch (type) { + case SNDRV_DEVICE_TYPE_SEQUENCER: + case SNDRV_DEVICE_TYPE_TIMER: + minor = type; + break; + case SNDRV_DEVICE_TYPE_CONTROL: + snd_assert(card != NULL, return -EINVAL); + minor = SNDRV_MINOR(card->number, type); + break; + case SNDRV_DEVICE_TYPE_HWDEP: + case SNDRV_DEVICE_TYPE_RAWMIDI: + case SNDRV_DEVICE_TYPE_PCM_PLAYBACK: + case SNDRV_DEVICE_TYPE_PCM_CAPTURE: + snd_assert(card != NULL, return -EINVAL); + minor = SNDRV_MINOR(card->number, type + dev); + break; + default: + return -EINVAL; + } + snd_assert(minor >= 0 && minor < SNDRV_OS_MINORS, return -EINVAL); + return minor; +} + +/** + * snd_register_device - Register the ALSA device file for the card + * @type: the device type, SNDRV_DEVICE_TYPE_XXX + * @card: the card instance + * @dev: the device index + * @reg: the snd_minor_t record + * @name: the device file name + * + * Registers an ALSA device file for the given card. + * The operators have to be set in reg parameter. + * + * Retrurns zero if successful, or a negative error code on failure. + */ +int snd_register_device(int type, snd_card_t * card, int dev, snd_minor_t * reg, const char *name) +{ + int minor = snd_kernel_minor(type, card, dev); + snd_minor_t *preg; + struct device *device = NULL; + + if (minor < 0) + return minor; + snd_assert(name, return -EINVAL); + preg = (snd_minor_t *)kmalloc(sizeof(snd_minor_t) + strlen(name) + 1, GFP_KERNEL); + if (preg == NULL) + return -ENOMEM; + *preg = *reg; + preg->number = minor; + preg->device = dev; + strcpy(preg->name, name); + down(&sound_mutex); + if (snd_minor_search(minor)) { + up(&sound_mutex); + kfree(preg); + return -EBUSY; + } + list_add_tail(&preg->list, &snd_minors_hash[SNDRV_MINOR_CARD(minor)]); + if (strncmp(name, "controlC", 8) || card->number >= cards_limit) + devfs_mk_cdev(MKDEV(major, minor), S_IFCHR | device_mode, "snd/%s", name); + if (card) + device = card->dev; + class_simple_device_add(sound_class, MKDEV(major, minor), device, name); + + up(&sound_mutex); + return 0; +} + +/** + * snd_unregister_device - unregister the device on the given card + * @type: the device type, SNDRV_DEVICE_TYPE_XXX + * @card: the card instance + * @dev: the device index + * + * Unregisters the device file already registered via + * snd_register_device(). + * + * Returns zero if sucecessful, or a negative error code on failure + */ +int snd_unregister_device(int type, snd_card_t * card, int dev) +{ + int minor = snd_kernel_minor(type, card, dev); + snd_minor_t *mptr; + + if (minor < 0) + return minor; + down(&sound_mutex); + if ((mptr = snd_minor_search(minor)) == NULL) { + up(&sound_mutex); + return -EINVAL; + } + + if (strncmp(mptr->name, "controlC", 8) || card->number >= cards_limit) /* created in sound.c */ + devfs_remove("snd/%s", mptr->name); + class_simple_device_remove(MKDEV(major, minor)); + + list_del(&mptr->list); + up(&sound_mutex); + kfree(mptr); + return 0; +} + +/* + * INFO PART + */ + +static snd_info_entry_t *snd_minor_info_entry = NULL; + +static void snd_minor_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + int card, device; + struct list_head *list; + snd_minor_t *mptr; + + down(&sound_mutex); + for (card = 0; card < SNDRV_CARDS; card++) { + list_for_each(list, &snd_minors_hash[card]) { + mptr = list_entry(list, snd_minor_t, list); + if (SNDRV_MINOR_DEVICE(mptr->number) != SNDRV_MINOR_SEQUENCER) { + if ((device = mptr->device) >= 0) + snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", mptr->number, card, device, mptr->comment); + else + snd_iprintf(buffer, "%3i: [%i] : %s\n", mptr->number, card, mptr->comment); + } else { + snd_iprintf(buffer, "%3i: : %s\n", mptr->number, mptr->comment); + } + } + } + up(&sound_mutex); +} + +int __init snd_minor_info_init(void) +{ + snd_info_entry_t *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL); + if (entry) { + entry->c.text.read_size = PAGE_SIZE; + entry->c.text.read = snd_minor_info_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_minor_info_entry = entry; + return 0; +} + +int __exit snd_minor_info_done(void) +{ + if (snd_minor_info_entry) + snd_info_unregister(snd_minor_info_entry); + return 0; +} + +/* + * INIT PART + */ + +static int __init alsa_sound_init(void) +{ + short controlnum; + int err; + int card; + + snd_major = major; + snd_ecards_limit = cards_limit; + for (card = 0; card < SNDRV_CARDS; card++) + INIT_LIST_HEAD(&snd_minors_hash[card]); + if ((err = snd_oss_init_module()) < 0) + return err; + devfs_mk_dir("snd"); + if (register_chrdev(major, "alsa", &snd_fops)) { + snd_printk(KERN_ERR "unable to register native major device number %d\n", major); + devfs_remove("snd"); + return -EIO; + } + snd_memory_init(); + if (snd_info_init() < 0) { + snd_memory_done(); + unregister_chrdev(major, "alsa"); + devfs_remove("snd"); + return -ENOMEM; + } + snd_info_minor_register(); + for (controlnum = 0; controlnum < cards_limit; controlnum++) + devfs_mk_cdev(MKDEV(major, controlnum<<5), S_IFCHR | device_mode, "snd/controlC%d", controlnum); +#ifndef MODULE + printk(KERN_INFO "Advanced Linux Sound Architecture Driver Version " CONFIG_SND_VERSION CONFIG_SND_DATE ".\n"); +#endif + return 0; +} + +static void __exit alsa_sound_exit(void) +{ + short controlnum; + + for (controlnum = 0; controlnum < cards_limit; controlnum++) + devfs_remove("snd/controlC%d", controlnum); + + snd_info_minor_unregister(); + snd_info_done(); + snd_memory_done(); + if (unregister_chrdev(major, "alsa") != 0) + snd_printk(KERN_ERR "unable to unregister major device number %d\n", major); + devfs_remove("snd"); +} + +module_init(alsa_sound_init) +module_exit(alsa_sound_exit) + + /* sound.c */ +EXPORT_SYMBOL(snd_major); +EXPORT_SYMBOL(snd_ecards_limit); +#if defined(CONFIG_KMOD) +EXPORT_SYMBOL(snd_request_card); +#endif +EXPORT_SYMBOL(snd_register_device); +EXPORT_SYMBOL(snd_unregister_device); +#if defined(CONFIG_SND_OSSEMUL) +EXPORT_SYMBOL(snd_register_oss_device); +EXPORT_SYMBOL(snd_unregister_oss_device); +#endif + /* memory.c */ +#ifdef CONFIG_SND_DEBUG_MEMORY +EXPORT_SYMBOL(snd_hidden_kmalloc); +EXPORT_SYMBOL(snd_hidden_kcalloc); +EXPORT_SYMBOL(snd_hidden_kfree); +EXPORT_SYMBOL(snd_hidden_vmalloc); +EXPORT_SYMBOL(snd_hidden_vfree); +#endif +EXPORT_SYMBOL(snd_kmalloc_strdup); +EXPORT_SYMBOL(copy_to_user_fromio); +EXPORT_SYMBOL(copy_from_user_toio); + /* init.c */ +EXPORT_SYMBOL(snd_cards); +#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) +EXPORT_SYMBOL(snd_mixer_oss_notify_callback); +#endif +EXPORT_SYMBOL(snd_card_new); +EXPORT_SYMBOL(snd_card_disconnect); +EXPORT_SYMBOL(snd_card_free); +EXPORT_SYMBOL(snd_card_free_in_thread); +EXPORT_SYMBOL(snd_card_register); +EXPORT_SYMBOL(snd_component_add); +EXPORT_SYMBOL(snd_card_file_add); +EXPORT_SYMBOL(snd_card_file_remove); +#ifdef CONFIG_PM +EXPORT_SYMBOL(snd_power_wait); +EXPORT_SYMBOL(snd_card_set_pm_callback); +#if defined(CONFIG_PM) && defined(CONFIG_SND_GENERIC_PM) +EXPORT_SYMBOL(snd_card_set_generic_pm_callback); +#endif +#ifdef CONFIG_PCI +EXPORT_SYMBOL(snd_card_pci_suspend); +EXPORT_SYMBOL(snd_card_pci_resume); +#endif +#endif + /* device.c */ +EXPORT_SYMBOL(snd_device_new); +EXPORT_SYMBOL(snd_device_register); +EXPORT_SYMBOL(snd_device_free); +EXPORT_SYMBOL(snd_device_free_all); + /* isadma.c */ +#ifdef CONFIG_ISA +EXPORT_SYMBOL(snd_dma_program); +EXPORT_SYMBOL(snd_dma_disable); +EXPORT_SYMBOL(snd_dma_pointer); +#endif + /* info.c */ +#ifdef CONFIG_PROC_FS +EXPORT_SYMBOL(snd_seq_root); +EXPORT_SYMBOL(snd_iprintf); +EXPORT_SYMBOL(snd_info_get_line); +EXPORT_SYMBOL(snd_info_get_str); +EXPORT_SYMBOL(snd_info_create_module_entry); +EXPORT_SYMBOL(snd_info_create_card_entry); +EXPORT_SYMBOL(snd_info_free_entry); +EXPORT_SYMBOL(snd_info_register); +EXPORT_SYMBOL(snd_info_unregister); +EXPORT_SYMBOL(snd_card_proc_new); +#endif + /* info_oss.c */ +#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS) +EXPORT_SYMBOL(snd_oss_info_register); +#endif + /* control.c */ +EXPORT_SYMBOL(snd_ctl_new); +EXPORT_SYMBOL(snd_ctl_new1); +EXPORT_SYMBOL(snd_ctl_free_one); +EXPORT_SYMBOL(snd_ctl_add); +EXPORT_SYMBOL(snd_ctl_remove); +EXPORT_SYMBOL(snd_ctl_remove_id); +EXPORT_SYMBOL(snd_ctl_rename_id); +EXPORT_SYMBOL(snd_ctl_find_numid); +EXPORT_SYMBOL(snd_ctl_find_id); +EXPORT_SYMBOL(snd_ctl_notify); +EXPORT_SYMBOL(snd_ctl_register_ioctl); +EXPORT_SYMBOL(snd_ctl_unregister_ioctl); +#ifdef CONFIG_COMPAT +EXPORT_SYMBOL(snd_ctl_register_ioctl_compat); +EXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat); +#endif +EXPORT_SYMBOL(snd_ctl_elem_read); +EXPORT_SYMBOL(snd_ctl_elem_write); + /* misc.c */ +EXPORT_SYMBOL(snd_task_name); +#ifdef CONFIG_SND_VERBOSE_PRINTK +EXPORT_SYMBOL(snd_verbose_printk); +#endif +#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_VERBOSE_PRINTK) +EXPORT_SYMBOL(snd_verbose_printd); +#endif + /* wrappers */ +#ifdef CONFIG_SND_DEBUG_MEMORY +EXPORT_SYMBOL(snd_wrapper_kmalloc); +EXPORT_SYMBOL(snd_wrapper_kfree); +EXPORT_SYMBOL(snd_wrapper_vmalloc); +EXPORT_SYMBOL(snd_wrapper_vfree); +#endif diff --git a/sound/core/sound_oss.c b/sound/core/sound_oss.c new file mode 100644 index 000000000000..de39d212bc15 --- /dev/null +++ b/sound/core/sound_oss.c @@ -0,0 +1,250 @@ +/* + * Advanced Linux Sound Architecture + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> + +#ifdef CONFIG_SND_OSSEMUL + +#if !defined(CONFIG_SOUND) && !(defined(MODULE) && defined(CONFIG_SOUND_MODULE)) +#error "Enable the OSS soundcore multiplexer (CONFIG_SOUND) in the kernel." +#endif + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <linux/sound.h> + +#define SNDRV_OS_MINORS 256 + +static struct list_head snd_oss_minors_hash[SNDRV_CARDS]; + +static DECLARE_MUTEX(sound_oss_mutex); + +static snd_minor_t *snd_oss_minor_search(int minor) +{ + struct list_head *list; + snd_minor_t *mptr; + + list_for_each(list, &snd_oss_minors_hash[SNDRV_MINOR_OSS_CARD(minor)]) { + mptr = list_entry(list, snd_minor_t, list); + if (mptr->number == minor) + return mptr; + } + return NULL; +} + +static int snd_oss_kernel_minor(int type, snd_card_t * card, int dev) +{ + int minor; + + switch (type) { + case SNDRV_OSS_DEVICE_TYPE_MIXER: + snd_assert(card != NULL && dev <= 1, return -EINVAL); + minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIXER1 : SNDRV_MINOR_OSS_MIXER)); + break; + case SNDRV_OSS_DEVICE_TYPE_SEQUENCER: + minor = SNDRV_MINOR_OSS_SEQUENCER; + break; + case SNDRV_OSS_DEVICE_TYPE_MUSIC: + minor = SNDRV_MINOR_OSS_MUSIC; + break; + case SNDRV_OSS_DEVICE_TYPE_PCM: + snd_assert(card != NULL && dev <= 1, return -EINVAL); + minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_PCM1 : SNDRV_MINOR_OSS_PCM)); + break; + case SNDRV_OSS_DEVICE_TYPE_MIDI: + snd_assert(card != NULL && dev <= 1, return -EINVAL); + minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIDI1 : SNDRV_MINOR_OSS_MIDI)); + break; + case SNDRV_OSS_DEVICE_TYPE_DMFM: + minor = SNDRV_MINOR_OSS(card->number, SNDRV_MINOR_OSS_DMFM); + break; + case SNDRV_OSS_DEVICE_TYPE_SNDSTAT: + minor = SNDRV_MINOR_OSS_SNDSTAT; + break; + default: + return -EINVAL; + } + snd_assert(minor >= 0 && minor < SNDRV_OS_MINORS, return -EINVAL); + return minor; +} + +int snd_register_oss_device(int type, snd_card_t * card, int dev, snd_minor_t * reg, const char *name) +{ + int minor = snd_oss_kernel_minor(type, card, dev); + int minor_unit; + snd_minor_t *preg; + int cidx = SNDRV_MINOR_OSS_CARD(minor); + int track2 = -1; + int register1 = -1, register2 = -1; + + if (minor < 0) + return minor; + preg = (snd_minor_t *)kmalloc(sizeof(snd_minor_t), GFP_KERNEL); + if (preg == NULL) + return -ENOMEM; + *preg = *reg; + preg->number = minor; + preg->device = dev; + down(&sound_oss_mutex); + list_add_tail(&preg->list, &snd_oss_minors_hash[cidx]); + minor_unit = SNDRV_MINOR_OSS_DEVICE(minor); + switch (minor_unit) { + case SNDRV_MINOR_OSS_PCM: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO); + break; + case SNDRV_MINOR_OSS_MIDI: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI); + break; + case SNDRV_MINOR_OSS_MIDI1: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1); + break; + } + register1 = register_sound_special(reg->f_ops, minor); + if (register1 != minor) + goto __end; + if (track2 >= 0) { + register2 = register_sound_special(reg->f_ops, track2); + if (register2 != track2) + goto __end; + } + up(&sound_oss_mutex); + return 0; + + __end: + if (register2 >= 0) + unregister_sound_special(register2); + if (register1 >= 0) + unregister_sound_special(register1); + list_del(&preg->list); + up(&sound_oss_mutex); + kfree(preg); + return -EBUSY; +} + +int snd_unregister_oss_device(int type, snd_card_t * card, int dev) +{ + int minor = snd_oss_kernel_minor(type, card, dev); + int cidx = SNDRV_MINOR_OSS_CARD(minor); + int track2 = -1; + snd_minor_t *mptr; + + if (minor < 0) + return minor; + down(&sound_oss_mutex); + mptr = snd_oss_minor_search(minor); + if (mptr == NULL) { + up(&sound_oss_mutex); + return -ENOENT; + } + unregister_sound_special(minor); + switch (SNDRV_MINOR_OSS_DEVICE(minor)) { + case SNDRV_MINOR_OSS_PCM: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO); + break; + case SNDRV_MINOR_OSS_MIDI: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI); + break; + case SNDRV_MINOR_OSS_MIDI1: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1); + break; + } + if (track2 >= 0) + unregister_sound_special(track2); + list_del(&mptr->list); + up(&sound_oss_mutex); + kfree(mptr); + return 0; +} + +/* + * INFO PART + */ + +#ifdef CONFIG_PROC_FS + +static snd_info_entry_t *snd_minor_info_oss_entry = NULL; + +static void snd_minor_info_oss_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + int card, dev; + struct list_head *list; + snd_minor_t *mptr; + + down(&sound_oss_mutex); + for (card = 0; card < SNDRV_CARDS; card++) { + list_for_each(list, &snd_oss_minors_hash[card]) { + mptr = list_entry(list, snd_minor_t, list); + dev = SNDRV_MINOR_OSS_DEVICE(mptr->number); + if (dev != SNDRV_MINOR_OSS_SNDSTAT && + dev != SNDRV_MINOR_OSS_SEQUENCER && + dev != SNDRV_MINOR_OSS_MUSIC) + snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", mptr->number, card, dev, mptr->comment); + else + snd_iprintf(buffer, "%3i: : %s\n", mptr->number, mptr->comment); + } + } + up(&sound_oss_mutex); +} + +#endif /* CONFIG_PROC_FS */ + +int __init snd_minor_info_oss_init(void) +{ +#ifdef CONFIG_PROC_FS + snd_info_entry_t *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "devices", snd_oss_root); + if (entry) { + entry->c.text.read_size = PAGE_SIZE; + entry->c.text.read = snd_minor_info_oss_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_minor_info_oss_entry = entry; +#endif + return 0; +} + +int __exit snd_minor_info_oss_done(void) +{ +#ifdef CONFIG_PROC_FS + if (snd_minor_info_oss_entry) + snd_info_unregister(snd_minor_info_oss_entry); +#endif + return 0; +} + +int __init snd_oss_init_module(void) +{ + int card; + + for (card = 0; card < SNDRV_CARDS; card++) + INIT_LIST_HEAD(&snd_oss_minors_hash[card]); + return 0; +} + +#endif /* CONFIG_SND_OSSEMUL */ diff --git a/sound/core/timer.c b/sound/core/timer.c new file mode 100644 index 000000000000..fa762ca439be --- /dev/null +++ b/sound/core/timer.c @@ -0,0 +1,1901 @@ +/* + * Timers abstract layer + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/timer.h> +#include <sound/control.h> +#include <sound/info.h> +#include <sound/minors.h> +#include <sound/initval.h> +#include <linux/kmod.h> +#ifdef CONFIG_KERNELD +#include <linux/kerneld.h> +#endif + +#if defined(CONFIG_SND_HPET) || defined(CONFIG_SND_HPET_MODULE) +#define DEFAULT_TIMER_LIMIT 3 +#elif defined(CONFIG_SND_RTCTIMER) || defined(CONFIG_SND_RTCTIMER_MODULE) +#define DEFAULT_TIMER_LIMIT 2 +#else +#define DEFAULT_TIMER_LIMIT 1 +#endif + +static int timer_limit = DEFAULT_TIMER_LIMIT; +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("ALSA timer interface"); +MODULE_LICENSE("GPL"); +module_param(timer_limit, int, 0444); +MODULE_PARM_DESC(timer_limit, "Maximum global timers in system."); + +typedef struct { + snd_timer_instance_t *timeri; + int tread; /* enhanced read with timestamps and events */ + unsigned long ticks; + unsigned long overrun; + int qhead; + int qtail; + int qused; + int queue_size; + snd_timer_read_t *queue; + snd_timer_tread_t *tqueue; + spinlock_t qlock; + unsigned long last_resolution; + unsigned int filter; + struct timespec tstamp; /* trigger tstamp */ + wait_queue_head_t qchange_sleep; + struct fasync_struct *fasync; +} snd_timer_user_t; + +/* list of timers */ +static LIST_HEAD(snd_timer_list); + +/* list of slave instances */ +static LIST_HEAD(snd_timer_slave_list); + +/* lock for slave active lists */ +static DEFINE_SPINLOCK(slave_active_lock); + +static DECLARE_MUTEX(register_mutex); + +static int snd_timer_free(snd_timer_t *timer); +static int snd_timer_dev_free(snd_device_t *device); +static int snd_timer_dev_register(snd_device_t *device); +static int snd_timer_dev_unregister(snd_device_t *device); + +static void snd_timer_reschedule(snd_timer_t * timer, unsigned long ticks_left); + +/* + * create a timer instance with the given owner string. + * when timer is not NULL, increments the module counter + */ +static snd_timer_instance_t *snd_timer_instance_new(char *owner, snd_timer_t *timer) +{ + snd_timer_instance_t *timeri; + timeri = kcalloc(1, sizeof(*timeri), GFP_KERNEL); + if (timeri == NULL) + return NULL; + timeri->owner = snd_kmalloc_strdup(owner, GFP_KERNEL); + if (! timeri->owner) { + kfree(timeri); + return NULL; + } + INIT_LIST_HEAD(&timeri->open_list); + INIT_LIST_HEAD(&timeri->active_list); + INIT_LIST_HEAD(&timeri->ack_list); + INIT_LIST_HEAD(&timeri->slave_list_head); + INIT_LIST_HEAD(&timeri->slave_active_head); + + timeri->timer = timer; + if (timer && timer->card && !try_module_get(timer->card->module)) { + kfree(timeri->owner); + kfree(timeri); + return NULL; + } + + return timeri; +} + +/* + * find a timer instance from the given timer id + */ +static snd_timer_t *snd_timer_find(snd_timer_id_t *tid) +{ + snd_timer_t *timer = NULL; + struct list_head *p; + + list_for_each(p, &snd_timer_list) { + timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list); + + if (timer->tmr_class != tid->dev_class) + continue; + if ((timer->tmr_class == SNDRV_TIMER_CLASS_CARD || + timer->tmr_class == SNDRV_TIMER_CLASS_PCM) && + (timer->card == NULL || + timer->card->number != tid->card)) + continue; + if (timer->tmr_device != tid->device) + continue; + if (timer->tmr_subdevice != tid->subdevice) + continue; + return timer; + } + return NULL; +} + +#ifdef CONFIG_KMOD + +static void snd_timer_request(snd_timer_id_t *tid) +{ + if (! current->fs->root) + return; + switch (tid->dev_class) { + case SNDRV_TIMER_CLASS_GLOBAL: + if (tid->device < timer_limit) + request_module("snd-timer-%i", tid->device); + break; + case SNDRV_TIMER_CLASS_CARD: + case SNDRV_TIMER_CLASS_PCM: + if (tid->card < snd_ecards_limit) + request_module("snd-card-%i", tid->card); + break; + default: + break; + } +} + +#endif + +/* + * look for a master instance matching with the slave id of the given slave. + * when found, relink the open_link of the slave. + * + * call this with register_mutex down. + */ +static void snd_timer_check_slave(snd_timer_instance_t *slave) +{ + snd_timer_t *timer; + snd_timer_instance_t *master; + struct list_head *p, *q; + + /* FIXME: it's really dumb to look up all entries.. */ + list_for_each(p, &snd_timer_list) { + timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list); + list_for_each(q, &timer->open_list_head) { + master = (snd_timer_instance_t *)list_entry(q, snd_timer_instance_t, open_list); + if (slave->slave_class == master->slave_class && + slave->slave_id == master->slave_id) { + list_del(&slave->open_list); + list_add_tail(&slave->open_list, &master->slave_list_head); + spin_lock_irq(&slave_active_lock); + slave->master = master; + slave->timer = master->timer; + spin_unlock_irq(&slave_active_lock); + return; + } + } + } +} + +/* + * look for slave instances matching with the slave id of the given master. + * when found, relink the open_link of slaves. + * + * call this with register_mutex down. + */ +static void snd_timer_check_master(snd_timer_instance_t *master) +{ + snd_timer_instance_t *slave; + struct list_head *p, *n; + + /* check all pending slaves */ + list_for_each_safe(p, n, &snd_timer_slave_list) { + slave = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, open_list); + if (slave->slave_class == master->slave_class && + slave->slave_id == master->slave_id) { + list_del(p); + list_add_tail(p, &master->slave_list_head); + spin_lock_irq(&slave_active_lock); + slave->master = master; + slave->timer = master->timer; + if (slave->flags & SNDRV_TIMER_IFLG_RUNNING) + list_add_tail(&slave->active_list, &master->slave_active_head); + spin_unlock_irq(&slave_active_lock); + } + } +} + +/* + * open a timer instance + * when opening a master, the slave id must be here given. + */ +int snd_timer_open(snd_timer_instance_t **ti, + char *owner, snd_timer_id_t *tid, + unsigned int slave_id) +{ + snd_timer_t *timer; + snd_timer_instance_t *timeri = NULL; + + if (tid->dev_class == SNDRV_TIMER_CLASS_SLAVE) { + /* open a slave instance */ + if (tid->dev_sclass <= SNDRV_TIMER_SCLASS_NONE || + tid->dev_sclass > SNDRV_TIMER_SCLASS_OSS_SEQUENCER) { + snd_printd("invalid slave class %i\n", tid->dev_sclass); + return -EINVAL; + } + down(®ister_mutex); + timeri = snd_timer_instance_new(owner, NULL); + timeri->slave_class = tid->dev_sclass; + timeri->slave_id = tid->device; + timeri->flags |= SNDRV_TIMER_IFLG_SLAVE; + list_add_tail(&timeri->open_list, &snd_timer_slave_list); + snd_timer_check_slave(timeri); + up(®ister_mutex); + *ti = timeri; + return 0; + } + + /* open a master instance */ + down(®ister_mutex); + timer = snd_timer_find(tid); +#ifdef CONFIG_KMOD + if (timer == NULL) { + up(®ister_mutex); + snd_timer_request(tid); + down(®ister_mutex); + timer = snd_timer_find(tid); + } +#endif + if (timer) { + if (!list_empty(&timer->open_list_head)) { + timeri = (snd_timer_instance_t *)list_entry(timer->open_list_head.next, snd_timer_instance_t, open_list); + if (timeri->flags & SNDRV_TIMER_IFLG_EXCLUSIVE) { + up(®ister_mutex); + return -EBUSY; + } + } + timeri = snd_timer_instance_new(owner, timer); + if (timeri) { + timeri->slave_class = tid->dev_sclass; + timeri->slave_id = slave_id; + if (list_empty(&timer->open_list_head) && timer->hw.open) + timer->hw.open(timer); + list_add_tail(&timeri->open_list, &timer->open_list_head); + snd_timer_check_master(timeri); + } + } else { + up(®ister_mutex); + return -ENODEV; + } + up(®ister_mutex); + *ti = timeri; + return 0; +} + +static int _snd_timer_stop(snd_timer_instance_t * timeri, int keep_flag, enum sndrv_timer_event event); + +/* + * close a timer instance + */ +int snd_timer_close(snd_timer_instance_t * timeri) +{ + snd_timer_t *timer = NULL; + struct list_head *p, *n; + snd_timer_instance_t *slave; + + snd_assert(timeri != NULL, return -ENXIO); + + /* force to stop the timer */ + snd_timer_stop(timeri); + + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) { + /* wait, until the active callback is finished */ + spin_lock_irq(&slave_active_lock); + while (timeri->flags & SNDRV_TIMER_IFLG_CALLBACK) { + spin_unlock_irq(&slave_active_lock); + udelay(10); + spin_lock_irq(&slave_active_lock); + } + spin_unlock_irq(&slave_active_lock); + down(®ister_mutex); + list_del(&timeri->open_list); + up(®ister_mutex); + } else { + timer = timeri->timer; + /* wait, until the active callback is finished */ + spin_lock_irq(&timer->lock); + while (timeri->flags & SNDRV_TIMER_IFLG_CALLBACK) { + spin_unlock_irq(&timer->lock); + udelay(10); + spin_lock_irq(&timer->lock); + } + spin_unlock_irq(&timer->lock); + down(®ister_mutex); + list_del(&timeri->open_list); + if (timer && list_empty(&timer->open_list_head) && timer->hw.close) + timer->hw.close(timer); + /* remove slave links */ + list_for_each_safe(p, n, &timeri->slave_list_head) { + slave = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, open_list); + spin_lock_irq(&slave_active_lock); + _snd_timer_stop(slave, 1, SNDRV_TIMER_EVENT_RESOLUTION); + list_del(p); + list_add_tail(p, &snd_timer_slave_list); + slave->master = NULL; + slave->timer = NULL; + spin_unlock_irq(&slave_active_lock); + } + up(®ister_mutex); + } + if (timeri->private_free) + timeri->private_free(timeri); + kfree(timeri->owner); + kfree(timeri); + if (timer && timer->card) + module_put(timer->card->module); + return 0; +} + +unsigned long snd_timer_resolution(snd_timer_instance_t * timeri) +{ + snd_timer_t * timer; + + if (timeri == NULL) + return 0; + if ((timer = timeri->timer) != NULL) { + if (timer->hw.c_resolution) + return timer->hw.c_resolution(timer); + return timer->hw.resolution; + } + return 0; +} + +static void snd_timer_notify1(snd_timer_instance_t *ti, enum sndrv_timer_event event) +{ + snd_timer_t *timer; + unsigned long flags; + unsigned long resolution = 0; + snd_timer_instance_t *ts; + struct list_head *n; + struct timespec tstamp; + + snd_timestamp_now(&tstamp, 1); + snd_assert(event >= SNDRV_TIMER_EVENT_START && event <= SNDRV_TIMER_EVENT_PAUSE, return); + if (event == SNDRV_TIMER_EVENT_START || event == SNDRV_TIMER_EVENT_CONTINUE) + resolution = snd_timer_resolution(ti); + if (ti->ccallback) + ti->ccallback(ti, SNDRV_TIMER_EVENT_START, &tstamp, resolution); + if (ti->flags & SNDRV_TIMER_IFLG_SLAVE) + return; + timer = ti->timer; + if (timer == NULL) + return; + if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) + return; + spin_lock_irqsave(&timer->lock, flags); + list_for_each(n, &ti->slave_active_head) { + ts = (snd_timer_instance_t *)list_entry(n, snd_timer_instance_t, active_list); + if (ts->ccallback) + ts->ccallback(ti, event + 100, &tstamp, resolution); + } + spin_unlock_irqrestore(&timer->lock, flags); +} + +static int snd_timer_start1(snd_timer_t *timer, snd_timer_instance_t *timeri, unsigned long sticks) +{ + list_del(&timeri->active_list); + list_add_tail(&timeri->active_list, &timer->active_list_head); + if (timer->running) { + if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) + goto __start_now; + timer->flags |= SNDRV_TIMER_FLG_RESCHED; + timeri->flags |= SNDRV_TIMER_IFLG_START; + return 1; /* delayed start */ + } else { + timer->sticks = sticks; + timer->hw.start(timer); + __start_now: + timer->running++; + timeri->flags |= SNDRV_TIMER_IFLG_RUNNING; + return 0; + } +} + +static int snd_timer_start_slave(snd_timer_instance_t *timeri) +{ + unsigned long flags; + + spin_lock_irqsave(&slave_active_lock, flags); + timeri->flags |= SNDRV_TIMER_IFLG_RUNNING; + if (timeri->master) + list_add_tail(&timeri->active_list, &timeri->master->slave_active_head); + spin_unlock_irqrestore(&slave_active_lock, flags); + return 1; /* delayed start */ +} + +/* + * start the timer instance + */ +int snd_timer_start(snd_timer_instance_t * timeri, unsigned int ticks) +{ + snd_timer_t *timer; + int result = -EINVAL; + unsigned long flags; + + if (timeri == NULL || ticks < 1) + return -EINVAL; + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) { + result = snd_timer_start_slave(timeri); + snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_START); + return result; + } + timer = timeri->timer; + if (timer == NULL) + return -EINVAL; + spin_lock_irqsave(&timer->lock, flags); + timeri->ticks = timeri->cticks = ticks; + timeri->pticks = 0; + result = snd_timer_start1(timer, timeri, ticks); + spin_unlock_irqrestore(&timer->lock, flags); + snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_START); + return result; +} + +static int _snd_timer_stop(snd_timer_instance_t * timeri, int keep_flag, enum sndrv_timer_event event) +{ + snd_timer_t *timer; + unsigned long flags; + + snd_assert(timeri != NULL, return -ENXIO); + + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) { + if (!keep_flag) { + spin_lock_irqsave(&slave_active_lock, flags); + timeri->flags &= ~SNDRV_TIMER_IFLG_RUNNING; + spin_unlock_irqrestore(&slave_active_lock, flags); + } + goto __end; + } + timer = timeri->timer; + if (!timer) + return -EINVAL; + spin_lock_irqsave(&timer->lock, flags); + list_del_init(&timeri->ack_list); + list_del_init(&timeri->active_list); + if ((timeri->flags & SNDRV_TIMER_IFLG_RUNNING) && + !(--timer->running)) { + timer->hw.stop(timer); + if (timer->flags & SNDRV_TIMER_FLG_RESCHED) { + timer->flags &= ~SNDRV_TIMER_FLG_RESCHED; + snd_timer_reschedule(timer, 0); + if (timer->flags & SNDRV_TIMER_FLG_CHANGE) { + timer->flags &= ~SNDRV_TIMER_FLG_CHANGE; + timer->hw.start(timer); + } + } + } + if (!keep_flag) + timeri->flags &= ~(SNDRV_TIMER_IFLG_RUNNING|SNDRV_TIMER_IFLG_START); + spin_unlock_irqrestore(&timer->lock, flags); + __end: + if (event != SNDRV_TIMER_EVENT_RESOLUTION) + snd_timer_notify1(timeri, event); + return 0; +} + +/* + * stop the timer instance. + * + * do not call this from the timer callback! + */ +int snd_timer_stop(snd_timer_instance_t * timeri) +{ + snd_timer_t *timer; + unsigned long flags; + int err; + + err = _snd_timer_stop(timeri, 0, SNDRV_TIMER_EVENT_STOP); + if (err < 0) + return err; + timer = timeri->timer; + spin_lock_irqsave(&timer->lock, flags); + timeri->cticks = timeri->ticks; + timeri->pticks = 0; + spin_unlock_irqrestore(&timer->lock, flags); + return 0; +} + +/* + * start again.. the tick is kept. + */ +int snd_timer_continue(snd_timer_instance_t * timeri) +{ + snd_timer_t *timer; + int result = -EINVAL; + unsigned long flags; + + if (timeri == NULL) + return result; + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + return snd_timer_start_slave(timeri); + timer = timeri->timer; + if (! timer) + return -EINVAL; + spin_lock_irqsave(&timer->lock, flags); + if (!timeri->cticks) + timeri->cticks = 1; + timeri->pticks = 0; + result = snd_timer_start1(timer, timeri, timer->sticks); + spin_unlock_irqrestore(&timer->lock, flags); + snd_timer_notify1(timeri, SNDRV_TIMER_EVENT_CONTINUE); + return result; +} + +/* + * pause.. remember the ticks left + */ +int snd_timer_pause(snd_timer_instance_t * timeri) +{ + return _snd_timer_stop(timeri, 0, SNDRV_TIMER_EVENT_PAUSE); +} + +/* + * reschedule the timer + * + * start pending instances and check the scheduling ticks. + * when the scheduling ticks is changed set CHANGE flag to reprogram the timer. + */ +static void snd_timer_reschedule(snd_timer_t * timer, unsigned long ticks_left) +{ + snd_timer_instance_t *ti; + unsigned long ticks = ~0UL; + struct list_head *p; + + list_for_each(p, &timer->active_list_head) { + ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, active_list); + if (ti->flags & SNDRV_TIMER_IFLG_START) { + ti->flags &= ~SNDRV_TIMER_IFLG_START; + ti->flags |= SNDRV_TIMER_IFLG_RUNNING; + timer->running++; + } + if (ti->flags & SNDRV_TIMER_IFLG_RUNNING) { + if (ticks > ti->cticks) + ticks = ti->cticks; + } + } + if (ticks == ~0UL) { + timer->flags &= ~SNDRV_TIMER_FLG_RESCHED; + return; + } + if (ticks > timer->hw.ticks) + ticks = timer->hw.ticks; + if (ticks_left != ticks) + timer->flags |= SNDRV_TIMER_FLG_CHANGE; + timer->sticks = ticks; +} + +/* + * timer tasklet + * + */ +static void snd_timer_tasklet(unsigned long arg) +{ + snd_timer_t *timer = (snd_timer_t *) arg; + snd_timer_instance_t *ti; + struct list_head *p; + unsigned long resolution, ticks; + + spin_lock(&timer->lock); + /* now process all callbacks */ + while (!list_empty(&timer->sack_list_head)) { + p = timer->sack_list_head.next; /* get first item */ + ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, ack_list); + + /* remove from ack_list and make empty */ + list_del_init(p); + + ticks = ti->pticks; + ti->pticks = 0; + resolution = ti->resolution; + + ti->flags |= SNDRV_TIMER_IFLG_CALLBACK; + spin_unlock(&timer->lock); + if (ti->callback) + ti->callback(ti, resolution, ticks); + spin_lock(&timer->lock); + ti->flags &= ~SNDRV_TIMER_IFLG_CALLBACK; + } + spin_unlock(&timer->lock); +} + +/* + * timer interrupt + * + * ticks_left is usually equal to timer->sticks. + * + */ +void snd_timer_interrupt(snd_timer_t * timer, unsigned long ticks_left) +{ + snd_timer_instance_t *ti, *ts; + unsigned long resolution, ticks; + struct list_head *p, *q, *n; + int use_tasklet = 0; + + if (timer == NULL) + return; + + spin_lock(&timer->lock); + + /* remember the current resolution */ + if (timer->hw.c_resolution) + resolution = timer->hw.c_resolution(timer); + else + resolution = timer->hw.resolution; + + /* loop for all active instances + * here we cannot use list_for_each because the active_list of a processed + * instance is relinked to done_list_head before callback is called. + */ + list_for_each_safe(p, n, &timer->active_list_head) { + ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, active_list); + if (!(ti->flags & SNDRV_TIMER_IFLG_RUNNING)) + continue; + ti->pticks += ticks_left; + ti->resolution = resolution; + if (ti->cticks < ticks_left) + ti->cticks = 0; + else + ti->cticks -= ticks_left; + if (ti->cticks) /* not expired */ + continue; + if (ti->flags & SNDRV_TIMER_IFLG_AUTO) { + ti->cticks = ti->ticks; + } else { + ti->flags &= ~SNDRV_TIMER_IFLG_RUNNING; + if (--timer->running) + list_del(p); + } + if (list_empty(&ti->ack_list)) { + if ((timer->hw.flags & SNDRV_TIMER_HW_TASKLET) || + (ti->flags & SNDRV_TIMER_IFLG_FAST)) { + list_add_tail(&ti->ack_list, &timer->ack_list_head); + } else { + list_add_tail(&ti->ack_list, &timer->sack_list_head); + } + } + list_for_each(q, &ti->slave_active_head) { + ts = (snd_timer_instance_t *)list_entry(q, snd_timer_instance_t, active_list); + ts->pticks = ti->pticks; + ts->resolution = resolution; + if (list_empty(&ts->ack_list)) { + if ((timer->hw.flags & SNDRV_TIMER_HW_TASKLET) || + (ti->flags & SNDRV_TIMER_IFLG_FAST)) { + list_add_tail(&ts->ack_list, &timer->ack_list_head); + } else { + list_add_tail(&ts->ack_list, &timer->sack_list_head); + } + } + } + } + if (timer->flags & SNDRV_TIMER_FLG_RESCHED) + snd_timer_reschedule(timer, ticks_left); + if (timer->running) { + if (timer->hw.flags & SNDRV_TIMER_HW_STOP) { + timer->hw.stop(timer); + timer->flags |= SNDRV_TIMER_FLG_CHANGE; + } + if (!(timer->hw.flags & SNDRV_TIMER_HW_AUTO) || + (timer->flags & SNDRV_TIMER_FLG_CHANGE)) { + /* restart timer */ + timer->flags &= ~SNDRV_TIMER_FLG_CHANGE; + timer->hw.start(timer); + } + } else { + timer->hw.stop(timer); + } + + /* now process all fast callbacks */ + while (!list_empty(&timer->ack_list_head)) { + p = timer->ack_list_head.next; /* get first item */ + ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, ack_list); + + /* remove from ack_list and make empty */ + list_del_init(p); + + ticks = ti->pticks; + ti->pticks = 0; + + ti->flags |= SNDRV_TIMER_IFLG_CALLBACK; + spin_unlock(&timer->lock); + if (ti->callback) + ti->callback(ti, resolution, ticks); + spin_lock(&timer->lock); + ti->flags &= ~SNDRV_TIMER_IFLG_CALLBACK; + } + + /* do we have any slow callbacks? */ + use_tasklet = !list_empty(&timer->sack_list_head); + spin_unlock(&timer->lock); + + if (use_tasklet) + tasklet_hi_schedule(&timer->task_queue); +} + +/* + + */ + +int snd_timer_new(snd_card_t *card, char *id, snd_timer_id_t *tid, snd_timer_t ** rtimer) +{ + snd_timer_t *timer; + int err; + static snd_device_ops_t ops = { + .dev_free = snd_timer_dev_free, + .dev_register = snd_timer_dev_register, + .dev_unregister = snd_timer_dev_unregister + }; + + snd_assert(tid != NULL, return -EINVAL); + snd_assert(rtimer != NULL, return -EINVAL); + *rtimer = NULL; + timer = kcalloc(1, sizeof(*timer), GFP_KERNEL); + if (timer == NULL) + return -ENOMEM; + timer->tmr_class = tid->dev_class; + timer->card = card; + timer->tmr_device = tid->device; + timer->tmr_subdevice = tid->subdevice; + if (id) + strlcpy(timer->id, id, sizeof(timer->id)); + INIT_LIST_HEAD(&timer->device_list); + INIT_LIST_HEAD(&timer->open_list_head); + INIT_LIST_HEAD(&timer->active_list_head); + INIT_LIST_HEAD(&timer->ack_list_head); + INIT_LIST_HEAD(&timer->sack_list_head); + spin_lock_init(&timer->lock); + tasklet_init(&timer->task_queue, snd_timer_tasklet, (unsigned long)timer); + if (card != NULL) { + if ((err = snd_device_new(card, SNDRV_DEV_TIMER, timer, &ops)) < 0) { + snd_timer_free(timer); + return err; + } + } + *rtimer = timer; + return 0; +} + +static int snd_timer_free(snd_timer_t *timer) +{ + snd_assert(timer != NULL, return -ENXIO); + if (timer->private_free) + timer->private_free(timer); + kfree(timer); + return 0; +} + +int snd_timer_dev_free(snd_device_t *device) +{ + snd_timer_t *timer = device->device_data; + return snd_timer_free(timer); +} + +int snd_timer_dev_register(snd_device_t *dev) +{ + snd_timer_t *timer = dev->device_data; + snd_timer_t *timer1; + struct list_head *p; + + snd_assert(timer != NULL && timer->hw.start != NULL && timer->hw.stop != NULL, return -ENXIO); + if (!(timer->hw.flags & SNDRV_TIMER_HW_SLAVE) && + !timer->hw.resolution && timer->hw.c_resolution == NULL) + return -EINVAL; + + down(®ister_mutex); + list_for_each(p, &snd_timer_list) { + timer1 = (snd_timer_t *)list_entry(p, snd_timer_t, device_list); + if (timer1->tmr_class > timer->tmr_class) + break; + if (timer1->tmr_class < timer->tmr_class) + continue; + if (timer1->card && timer->card) { + if (timer1->card->number > timer->card->number) + break; + if (timer1->card->number < timer->card->number) + continue; + } + if (timer1->tmr_device > timer->tmr_device) + break; + if (timer1->tmr_device < timer->tmr_device) + continue; + if (timer1->tmr_subdevice > timer->tmr_subdevice) + break; + if (timer1->tmr_subdevice < timer->tmr_subdevice) + continue; + /* conflicts.. */ + up(®ister_mutex); + return -EBUSY; + } + list_add_tail(&timer->device_list, p); + up(®ister_mutex); + return 0; +} + +int snd_timer_unregister(snd_timer_t *timer) +{ + struct list_head *p, *n; + snd_timer_instance_t *ti; + + snd_assert(timer != NULL, return -ENXIO); + down(®ister_mutex); + if (! list_empty(&timer->open_list_head)) { + snd_printk(KERN_WARNING "timer 0x%lx is busy?\n", (long)timer); + list_for_each_safe(p, n, &timer->open_list_head) { + list_del_init(p); + ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, open_list); + ti->timer = NULL; + } + } + list_del(&timer->device_list); + up(®ister_mutex); + return snd_timer_free(timer); +} + +static int snd_timer_dev_unregister(snd_device_t *device) +{ + snd_timer_t *timer = device->device_data; + return snd_timer_unregister(timer); +} + +void snd_timer_notify(snd_timer_t *timer, enum sndrv_timer_event event, struct timespec *tstamp) +{ + unsigned long flags; + unsigned long resolution = 0; + snd_timer_instance_t *ti, *ts; + struct list_head *p, *n; + + snd_runtime_check(timer->hw.flags & SNDRV_TIMER_HW_SLAVE, return); + snd_assert(event >= SNDRV_TIMER_EVENT_MSTART && event <= SNDRV_TIMER_EVENT_MPAUSE, return); + spin_lock_irqsave(&timer->lock, flags); + if (event == SNDRV_TIMER_EVENT_MSTART || event == SNDRV_TIMER_EVENT_MCONTINUE) { + if (timer->hw.c_resolution) + resolution = timer->hw.c_resolution(timer); + else + resolution = timer->hw.resolution; + } + list_for_each(p, &timer->active_list_head) { + ti = (snd_timer_instance_t *)list_entry(p, snd_timer_instance_t, active_list); + if (ti->ccallback) + ti->ccallback(ti, event, tstamp, resolution); + list_for_each(n, &ti->slave_active_head) { + ts = (snd_timer_instance_t *)list_entry(n, snd_timer_instance_t, active_list); + if (ts->ccallback) + ts->ccallback(ts, event, tstamp, resolution); + } + } + spin_unlock_irqrestore(&timer->lock, flags); +} + +/* + * exported functions for global timers + */ +int snd_timer_global_new(char *id, int device, snd_timer_t **rtimer) +{ + snd_timer_id_t tid; + + tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL; + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.card = -1; + tid.device = device; + tid.subdevice = 0; + return snd_timer_new(NULL, id, &tid, rtimer); +} + +int snd_timer_global_free(snd_timer_t *timer) +{ + return snd_timer_free(timer); +} + +int snd_timer_global_register(snd_timer_t *timer) +{ + snd_device_t dev; + + memset(&dev, 0, sizeof(dev)); + dev.device_data = timer; + return snd_timer_dev_register(&dev); +} + +int snd_timer_global_unregister(snd_timer_t *timer) +{ + return snd_timer_unregister(timer); +} + +/* + * System timer + */ + +struct snd_timer_system_private { + struct timer_list tlist; + struct timer * timer; + unsigned long last_expires; + unsigned long last_jiffies; + unsigned long correction; +}; + +unsigned int snd_timer_system_resolution(void) +{ + return 1000000000L / HZ; +} + +static void snd_timer_s_function(unsigned long data) +{ + snd_timer_t *timer = (snd_timer_t *)data; + struct snd_timer_system_private *priv = timer->private_data; + unsigned long jiff = jiffies; + if (time_after(jiff, priv->last_expires)) + priv->correction = (long)jiff - (long)priv->last_expires; + snd_timer_interrupt(timer, (long)jiff - (long)priv->last_jiffies); +} + +static int snd_timer_s_start(snd_timer_t * timer) +{ + struct snd_timer_system_private *priv; + unsigned long njiff; + + priv = (struct snd_timer_system_private *) timer->private_data; + njiff = (priv->last_jiffies = jiffies); + if (priv->correction > timer->sticks - 1) { + priv->correction -= timer->sticks - 1; + njiff++; + } else { + njiff += timer->sticks - priv->correction; + priv->correction -= timer->sticks; + } + priv->last_expires = priv->tlist.expires = njiff; + add_timer(&priv->tlist); + return 0; +} + +static int snd_timer_s_stop(snd_timer_t * timer) +{ + struct snd_timer_system_private *priv; + unsigned long jiff; + + priv = (struct snd_timer_system_private *) timer->private_data; + del_timer(&priv->tlist); + jiff = jiffies; + if (time_before(jiff, priv->last_expires)) + timer->sticks = priv->last_expires - jiff; + else + timer->sticks = 1; + return 0; +} + +static struct _snd_timer_hardware snd_timer_system = +{ + .flags = SNDRV_TIMER_HW_FIRST | SNDRV_TIMER_HW_TASKLET, + .resolution = 1000000000L / HZ, + .ticks = 10000000L, + .start = snd_timer_s_start, + .stop = snd_timer_s_stop +}; + +static void snd_timer_free_system(snd_timer_t *timer) +{ + kfree(timer->private_data); +} + +static int snd_timer_register_system(void) +{ + snd_timer_t *timer; + struct snd_timer_system_private *priv; + int err; + + if ((err = snd_timer_global_new("system", SNDRV_TIMER_GLOBAL_SYSTEM, &timer)) < 0) + return err; + strcpy(timer->name, "system timer"); + timer->hw = snd_timer_system; + priv = kcalloc(1, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + snd_timer_free(timer); + return -ENOMEM; + } + init_timer(&priv->tlist); + priv->tlist.function = snd_timer_s_function; + priv->tlist.data = (unsigned long) timer; + timer->private_data = priv; + timer->private_free = snd_timer_free_system; + return snd_timer_global_register(timer); +} + +/* + * Info interface + */ + +static void snd_timer_proc_read(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + unsigned long flags; + snd_timer_t *timer; + snd_timer_instance_t *ti; + struct list_head *p, *q; + + down(®ister_mutex); + list_for_each(p, &snd_timer_list) { + timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list); + switch (timer->tmr_class) { + case SNDRV_TIMER_CLASS_GLOBAL: + snd_iprintf(buffer, "G%i: ", timer->tmr_device); + break; + case SNDRV_TIMER_CLASS_CARD: + snd_iprintf(buffer, "C%i-%i: ", timer->card->number, timer->tmr_device); + break; + case SNDRV_TIMER_CLASS_PCM: + snd_iprintf(buffer, "P%i-%i-%i: ", timer->card->number, timer->tmr_device, timer->tmr_subdevice); + break; + default: + snd_iprintf(buffer, "?%i-%i-%i-%i: ", timer->tmr_class, timer->card ? timer->card->number : -1, timer->tmr_device, timer->tmr_subdevice); + } + snd_iprintf(buffer, "%s :", timer->name); + if (timer->hw.resolution) + snd_iprintf(buffer, " %lu.%03luus (%lu ticks)", timer->hw.resolution / 1000, timer->hw.resolution % 1000, timer->hw.ticks); + if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) + snd_iprintf(buffer, " SLAVE"); + snd_iprintf(buffer, "\n"); + spin_lock_irqsave(&timer->lock, flags); + list_for_each(q, &timer->open_list_head) { + ti = (snd_timer_instance_t *)list_entry(q, snd_timer_instance_t, open_list); + snd_iprintf(buffer, " Client %s : %s : lost interrupts %li\n", + ti->owner ? ti->owner : "unknown", + ti->flags & (SNDRV_TIMER_IFLG_START|SNDRV_TIMER_IFLG_RUNNING) ? "running" : "stopped", + ti->lost); + } + spin_unlock_irqrestore(&timer->lock, flags); + } + up(®ister_mutex); +} + +/* + * USER SPACE interface + */ + +static void snd_timer_user_interrupt(snd_timer_instance_t *timeri, + unsigned long resolution, + unsigned long ticks) +{ + snd_timer_user_t *tu = timeri->callback_data; + snd_timer_read_t *r; + int prev; + + spin_lock(&tu->qlock); + if (tu->qused > 0) { + prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1; + r = &tu->queue[prev]; + if (r->resolution == resolution) { + r->ticks += ticks; + goto __wake; + } + } + if (tu->qused >= tu->queue_size) { + tu->overrun++; + } else { + r = &tu->queue[tu->qtail++]; + tu->qtail %= tu->queue_size; + r->resolution = resolution; + r->ticks = ticks; + tu->qused++; + } + __wake: + spin_unlock(&tu->qlock); + kill_fasync(&tu->fasync, SIGIO, POLL_IN); + wake_up(&tu->qchange_sleep); +} + +static void snd_timer_user_append_to_tqueue(snd_timer_user_t *tu, snd_timer_tread_t *tread) +{ + if (tu->qused >= tu->queue_size) { + tu->overrun++; + } else { + memcpy(&tu->tqueue[tu->qtail++], tread, sizeof(*tread)); + tu->qtail %= tu->queue_size; + tu->qused++; + } +} + +static void snd_timer_user_ccallback(snd_timer_instance_t *timeri, + enum sndrv_timer_event event, + struct timespec *tstamp, + unsigned long resolution) +{ + snd_timer_user_t *tu = timeri->callback_data; + snd_timer_tread_t r1; + + if (event >= SNDRV_TIMER_EVENT_START && event <= SNDRV_TIMER_EVENT_PAUSE) + tu->tstamp = *tstamp; + if ((tu->filter & (1 << event)) == 0 || !tu->tread) + return; + r1.event = event; + r1.tstamp = *tstamp; + r1.val = resolution; + spin_lock(&tu->qlock); + snd_timer_user_append_to_tqueue(tu, &r1); + spin_unlock(&tu->qlock); + kill_fasync(&tu->fasync, SIGIO, POLL_IN); + wake_up(&tu->qchange_sleep); +} + +static void snd_timer_user_tinterrupt(snd_timer_instance_t *timeri, + unsigned long resolution, + unsigned long ticks) +{ + snd_timer_user_t *tu = timeri->callback_data; + snd_timer_tread_t *r, r1; + struct timespec tstamp; + int prev, append = 0; + + snd_timestamp_zero(&tstamp); + spin_lock(&tu->qlock); + if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION)|(1 << SNDRV_TIMER_EVENT_TICK))) == 0) { + spin_unlock(&tu->qlock); + return; + } + if (tu->last_resolution != resolution || ticks > 0) + snd_timestamp_now(&tstamp, 1); + if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) && tu->last_resolution != resolution) { + r1.event = SNDRV_TIMER_EVENT_RESOLUTION; + r1.tstamp = tstamp; + r1.val = resolution; + snd_timer_user_append_to_tqueue(tu, &r1); + tu->last_resolution = resolution; + append++; + } + if ((tu->filter & (1 << SNDRV_TIMER_EVENT_TICK)) == 0) + goto __wake; + if (ticks == 0) + goto __wake; + if (tu->qused > 0) { + prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1; + r = &tu->tqueue[prev]; + if (r->event == SNDRV_TIMER_EVENT_TICK) { + r->tstamp = tstamp; + r->val += ticks; + append++; + goto __wake; + } + } + r1.event = SNDRV_TIMER_EVENT_TICK; + r1.tstamp = tstamp; + r1.val = ticks; + snd_timer_user_append_to_tqueue(tu, &r1); + append++; + __wake: + spin_unlock(&tu->qlock); + if (append == 0) + return; + kill_fasync(&tu->fasync, SIGIO, POLL_IN); + wake_up(&tu->qchange_sleep); +} + +static int snd_timer_user_open(struct inode *inode, struct file *file) +{ + snd_timer_user_t *tu; + + tu = kcalloc(1, sizeof(*tu), GFP_KERNEL); + if (tu == NULL) + return -ENOMEM; + spin_lock_init(&tu->qlock); + init_waitqueue_head(&tu->qchange_sleep); + tu->ticks = 1; + tu->queue_size = 128; + tu->queue = (snd_timer_read_t *)kmalloc(tu->queue_size * sizeof(snd_timer_read_t), GFP_KERNEL); + if (tu->queue == NULL) { + kfree(tu); + return -ENOMEM; + } + file->private_data = tu; + return 0; +} + +static int snd_timer_user_release(struct inode *inode, struct file *file) +{ + snd_timer_user_t *tu; + + if (file->private_data) { + tu = file->private_data; + file->private_data = NULL; + fasync_helper(-1, file, 0, &tu->fasync); + if (tu->timeri) + snd_timer_close(tu->timeri); + kfree(tu->queue); + kfree(tu->tqueue); + kfree(tu); + } + return 0; +} + +static void snd_timer_user_zero_id(snd_timer_id_t *id) +{ + id->dev_class = SNDRV_TIMER_CLASS_NONE; + id->dev_sclass = SNDRV_TIMER_SCLASS_NONE; + id->card = -1; + id->device = -1; + id->subdevice = -1; +} + +static void snd_timer_user_copy_id(snd_timer_id_t *id, snd_timer_t *timer) +{ + id->dev_class = timer->tmr_class; + id->dev_sclass = SNDRV_TIMER_SCLASS_NONE; + id->card = timer->card ? timer->card->number : -1; + id->device = timer->tmr_device; + id->subdevice = timer->tmr_subdevice; +} + +static int snd_timer_user_next_device(snd_timer_id_t __user *_tid) +{ + snd_timer_id_t id; + snd_timer_t *timer; + struct list_head *p; + + if (copy_from_user(&id, _tid, sizeof(id))) + return -EFAULT; + down(®ister_mutex); + if (id.dev_class < 0) { /* first item */ + if (list_empty(&snd_timer_list)) + snd_timer_user_zero_id(&id); + else { + timer = (snd_timer_t *)list_entry(snd_timer_list.next, snd_timer_t, device_list); + snd_timer_user_copy_id(&id, timer); + } + } else { + switch (id.dev_class) { + case SNDRV_TIMER_CLASS_GLOBAL: + id.device = id.device < 0 ? 0 : id.device + 1; + list_for_each(p, &snd_timer_list) { + timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list); + if (timer->tmr_class > SNDRV_TIMER_CLASS_GLOBAL) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_device >= id.device) { + snd_timer_user_copy_id(&id, timer); + break; + } + } + if (p == &snd_timer_list) + snd_timer_user_zero_id(&id); + break; + case SNDRV_TIMER_CLASS_CARD: + case SNDRV_TIMER_CLASS_PCM: + if (id.card < 0) { + id.card = 0; + } else { + if (id.card < 0) { + id.card = 0; + } else { + if (id.device < 0) { + id.device = 0; + } else { + id.subdevice = id.subdevice < 0 ? 0 : id.subdevice + 1; + } + } + } + list_for_each(p, &snd_timer_list) { + timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list); + if (timer->tmr_class > id.dev_class) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_class < id.dev_class) + continue; + if (timer->card->number > id.card) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->card->number < id.card) + continue; + if (timer->tmr_device > id.device) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_device < id.device) + continue; + if (timer->tmr_subdevice > id.subdevice) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_subdevice < id.subdevice) + continue; + snd_timer_user_copy_id(&id, timer); + break; + } + if (p == &snd_timer_list) + snd_timer_user_zero_id(&id); + break; + default: + snd_timer_user_zero_id(&id); + } + } + up(®ister_mutex); + if (copy_to_user(_tid, &id, sizeof(*_tid))) + return -EFAULT; + return 0; +} + +static int snd_timer_user_ginfo(struct file *file, snd_timer_ginfo_t __user *_ginfo) +{ + snd_timer_ginfo_t *ginfo; + snd_timer_id_t tid; + snd_timer_t *t; + struct list_head *p; + int err = 0; + + ginfo = kmalloc(sizeof(*ginfo), GFP_KERNEL); + if (! ginfo) + return -ENOMEM; + if (copy_from_user(ginfo, _ginfo, sizeof(*ginfo))) { + kfree(ginfo); + return -EFAULT; + } + tid = ginfo->tid; + memset(ginfo, 0, sizeof(*ginfo)); + ginfo->tid = tid; + down(®ister_mutex); + t = snd_timer_find(&tid); + if (t != NULL) { + ginfo->card = t->card ? t->card->number : -1; + if (t->hw.flags & SNDRV_TIMER_HW_SLAVE) + ginfo->flags |= SNDRV_TIMER_FLG_SLAVE; + strlcpy(ginfo->id, t->id, sizeof(ginfo->id)); + strlcpy(ginfo->name, t->name, sizeof(ginfo->name)); + ginfo->resolution = t->hw.resolution; + if (t->hw.resolution_min > 0) { + ginfo->resolution_min = t->hw.resolution_min; + ginfo->resolution_max = t->hw.resolution_max; + } + list_for_each(p, &t->open_list_head) { + ginfo->clients++; + } + } else { + err = -ENODEV; + } + up(®ister_mutex); + if (err >= 0 && copy_to_user(_ginfo, ginfo, sizeof(*ginfo))) + err = -EFAULT; + kfree(ginfo); + return err; +} + +static int snd_timer_user_gparams(struct file *file, snd_timer_gparams_t __user *_gparams) +{ + snd_timer_gparams_t gparams; + snd_timer_t *t; + int err; + + if (copy_from_user(&gparams, _gparams, sizeof(gparams))) + return -EFAULT; + down(®ister_mutex); + t = snd_timer_find(&gparams.tid); + if (t != NULL) { + if (list_empty(&t->open_list_head)) { + if (t->hw.set_period) + err = t->hw.set_period(t, gparams.period_num, gparams.period_den); + else + err = -ENOSYS; + } else { + err = -EBUSY; + } + } else { + err = -ENODEV; + } + up(®ister_mutex); + return err; +} + +static int snd_timer_user_gstatus(struct file *file, snd_timer_gstatus_t __user *_gstatus) +{ + snd_timer_gstatus_t gstatus; + snd_timer_id_t tid; + snd_timer_t *t; + int err = 0; + + if (copy_from_user(&gstatus, _gstatus, sizeof(gstatus))) + return -EFAULT; + tid = gstatus.tid; + memset(&gstatus, 0, sizeof(gstatus)); + gstatus.tid = tid; + down(®ister_mutex); + t = snd_timer_find(&tid); + if (t != NULL) { + if (t->hw.c_resolution) + gstatus.resolution = t->hw.c_resolution(t); + else + gstatus.resolution = t->hw.resolution; + if (t->hw.precise_resolution) { + t->hw.precise_resolution(t, &gstatus.resolution_num, &gstatus.resolution_den); + } else { + gstatus.resolution_num = gstatus.resolution; + gstatus.resolution_den = 1000000000uL; + } + } else { + err = -ENODEV; + } + up(®ister_mutex); + if (err >= 0 && copy_to_user(_gstatus, &gstatus, sizeof(gstatus))) + err = -EFAULT; + return err; +} + +static int snd_timer_user_tselect(struct file *file, snd_timer_select_t __user *_tselect) +{ + snd_timer_user_t *tu; + snd_timer_select_t tselect; + char str[32]; + int err; + + tu = file->private_data; + if (tu->timeri) + snd_timer_close(tu->timeri); + if (copy_from_user(&tselect, _tselect, sizeof(tselect))) + return -EFAULT; + sprintf(str, "application %i", current->pid); + if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE) + tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION; + if ((err = snd_timer_open(&tu->timeri, str, &tselect.id, current->pid)) < 0) + return err; + + if (tu->queue) { + kfree(tu->queue); + tu->queue = NULL; + } + if (tu->tqueue) { + kfree(tu->tqueue); + tu->tqueue = NULL; + } + if (tu->tread) { + tu->tqueue = (snd_timer_tread_t *)kmalloc(tu->queue_size * sizeof(snd_timer_tread_t), GFP_KERNEL); + if (tu->tqueue == NULL) { + snd_timer_close(tu->timeri); + return -ENOMEM; + } + } else { + tu->queue = (snd_timer_read_t *)kmalloc(tu->queue_size * sizeof(snd_timer_read_t), GFP_KERNEL); + if (tu->queue == NULL) { + snd_timer_close(tu->timeri); + return -ENOMEM; + } + } + + tu->timeri->flags |= SNDRV_TIMER_IFLG_FAST; + tu->timeri->callback = tu->tread ? snd_timer_user_tinterrupt : snd_timer_user_interrupt; + tu->timeri->ccallback = snd_timer_user_ccallback; + tu->timeri->callback_data = (void *)tu; + return 0; +} + +static int snd_timer_user_info(struct file *file, snd_timer_info_t __user *_info) +{ + snd_timer_user_t *tu; + snd_timer_info_t *info; + snd_timer_t *t; + int err = 0; + + tu = file->private_data; + snd_assert(tu->timeri != NULL, return -ENXIO); + t = tu->timeri->timer; + snd_assert(t != NULL, return -ENXIO); + + info = kcalloc(1, sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + info->card = t->card ? t->card->number : -1; + if (t->hw.flags & SNDRV_TIMER_HW_SLAVE) + info->flags |= SNDRV_TIMER_FLG_SLAVE; + strlcpy(info->id, t->id, sizeof(info->id)); + strlcpy(info->name, t->name, sizeof(info->name)); + info->resolution = t->hw.resolution; + if (copy_to_user(_info, info, sizeof(*_info))) + err = -EFAULT; + kfree(info); + return err; +} + +static int snd_timer_user_params(struct file *file, snd_timer_params_t __user *_params) +{ + snd_timer_user_t *tu; + snd_timer_params_t params; + snd_timer_t *t; + snd_timer_read_t *tr; + snd_timer_tread_t *ttr; + int err; + + tu = file->private_data; + snd_assert(tu->timeri != NULL, return -ENXIO); + t = tu->timeri->timer; + snd_assert(t != NULL, return -ENXIO); + if (copy_from_user(¶ms, _params, sizeof(params))) + return -EFAULT; + if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE) && params.ticks < 1) { + err = -EINVAL; + goto _end; + } + if (params.queue_size > 0 && (params.queue_size < 32 || params.queue_size > 1024)) { + err = -EINVAL; + goto _end; + } + if (params.filter & ~((1<<SNDRV_TIMER_EVENT_RESOLUTION)| + (1<<SNDRV_TIMER_EVENT_TICK)| + (1<<SNDRV_TIMER_EVENT_START)| + (1<<SNDRV_TIMER_EVENT_STOP)| + (1<<SNDRV_TIMER_EVENT_CONTINUE)| + (1<<SNDRV_TIMER_EVENT_PAUSE)| + (1<<SNDRV_TIMER_EVENT_MSTART)| + (1<<SNDRV_TIMER_EVENT_MSTOP)| + (1<<SNDRV_TIMER_EVENT_MCONTINUE)| + (1<<SNDRV_TIMER_EVENT_MPAUSE))) { + err = -EINVAL; + goto _end; + } + snd_timer_stop(tu->timeri); + spin_lock_irq(&t->lock); + tu->timeri->flags &= ~(SNDRV_TIMER_IFLG_AUTO| + SNDRV_TIMER_IFLG_EXCLUSIVE| + SNDRV_TIMER_IFLG_EARLY_EVENT); + if (params.flags & SNDRV_TIMER_PSFLG_AUTO) + tu->timeri->flags |= SNDRV_TIMER_IFLG_AUTO; + if (params.flags & SNDRV_TIMER_PSFLG_EXCLUSIVE) + tu->timeri->flags |= SNDRV_TIMER_IFLG_EXCLUSIVE; + if (params.flags & SNDRV_TIMER_PSFLG_EARLY_EVENT) + tu->timeri->flags |= SNDRV_TIMER_IFLG_EARLY_EVENT; + spin_unlock_irq(&t->lock); + if (params.queue_size > 0 && (unsigned int)tu->queue_size != params.queue_size) { + if (tu->tread) { + ttr = (snd_timer_tread_t *)kmalloc(params.queue_size * sizeof(snd_timer_tread_t), GFP_KERNEL); + if (ttr) { + kfree(tu->tqueue); + tu->queue_size = params.queue_size; + tu->tqueue = ttr; + } + } else { + tr = (snd_timer_read_t *)kmalloc(params.queue_size * sizeof(snd_timer_read_t), GFP_KERNEL); + if (tr) { + kfree(tu->queue); + tu->queue_size = params.queue_size; + tu->queue = tr; + } + } + } + tu->qhead = tu->qtail = tu->qused = 0; + if (tu->timeri->flags & SNDRV_TIMER_IFLG_EARLY_EVENT) { + if (tu->tread) { + snd_timer_tread_t tread; + tread.event = SNDRV_TIMER_EVENT_EARLY; + tread.tstamp.tv_sec = 0; + tread.tstamp.tv_nsec = 0; + tread.val = 0; + snd_timer_user_append_to_tqueue(tu, &tread); + } else { + snd_timer_read_t *r = &tu->queue[0]; + r->resolution = 0; + r->ticks = 0; + tu->qused++; + tu->qtail++; + } + + } + tu->filter = params.filter; + tu->ticks = params.ticks; + err = 0; + _end: + if (copy_to_user(_params, ¶ms, sizeof(params))) + return -EFAULT; + return err; +} + +static int snd_timer_user_status(struct file *file, snd_timer_status_t __user *_status) +{ + snd_timer_user_t *tu; + snd_timer_status_t status; + + tu = file->private_data; + snd_assert(tu->timeri != NULL, return -ENXIO); + memset(&status, 0, sizeof(status)); + status.tstamp = tu->tstamp; + status.resolution = snd_timer_resolution(tu->timeri); + status.lost = tu->timeri->lost; + status.overrun = tu->overrun; + spin_lock_irq(&tu->qlock); + status.queue = tu->qused; + spin_unlock_irq(&tu->qlock); + if (copy_to_user(_status, &status, sizeof(status))) + return -EFAULT; + return 0; +} + +static int snd_timer_user_start(struct file *file) +{ + int err; + snd_timer_user_t *tu; + + tu = file->private_data; + snd_assert(tu->timeri != NULL, return -ENXIO); + snd_timer_stop(tu->timeri); + tu->timeri->lost = 0; + tu->last_resolution = 0; + return (err = snd_timer_start(tu->timeri, tu->ticks)) < 0 ? err : 0; +} + +static int snd_timer_user_stop(struct file *file) +{ + int err; + snd_timer_user_t *tu; + + tu = file->private_data; + snd_assert(tu->timeri != NULL, return -ENXIO); + return (err = snd_timer_stop(tu->timeri)) < 0 ? err : 0; +} + +static int snd_timer_user_continue(struct file *file) +{ + int err; + snd_timer_user_t *tu; + + tu = file->private_data; + snd_assert(tu->timeri != NULL, return -ENXIO); + tu->timeri->lost = 0; + return (err = snd_timer_continue(tu->timeri)) < 0 ? err : 0; +} + +static long snd_timer_user_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_timer_user_t *tu; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + tu = file->private_data; + switch (cmd) { + case SNDRV_TIMER_IOCTL_PVERSION: + return put_user(SNDRV_TIMER_VERSION, p) ? -EFAULT : 0; + case SNDRV_TIMER_IOCTL_NEXT_DEVICE: + return snd_timer_user_next_device(argp); + case SNDRV_TIMER_IOCTL_TREAD: + { + int xarg; + + if (tu->timeri) /* too late */ + return -EBUSY; + if (get_user(xarg, p)) + return -EFAULT; + tu->tread = xarg ? 1 : 0; + return 0; + } + case SNDRV_TIMER_IOCTL_GINFO: + return snd_timer_user_ginfo(file, argp); + case SNDRV_TIMER_IOCTL_GPARAMS: + return snd_timer_user_gparams(file, argp); + case SNDRV_TIMER_IOCTL_GSTATUS: + return snd_timer_user_gstatus(file, argp); + case SNDRV_TIMER_IOCTL_SELECT: + return snd_timer_user_tselect(file, argp); + case SNDRV_TIMER_IOCTL_INFO: + return snd_timer_user_info(file, argp); + case SNDRV_TIMER_IOCTL_PARAMS: + return snd_timer_user_params(file, argp); + case SNDRV_TIMER_IOCTL_STATUS: + return snd_timer_user_status(file, argp); + case SNDRV_TIMER_IOCTL_START: + return snd_timer_user_start(file); + case SNDRV_TIMER_IOCTL_STOP: + return snd_timer_user_stop(file); + case SNDRV_TIMER_IOCTL_CONTINUE: + return snd_timer_user_continue(file); + } + return -ENOTTY; +} + +static int snd_timer_user_fasync(int fd, struct file * file, int on) +{ + snd_timer_user_t *tu; + int err; + + tu = file->private_data; + err = fasync_helper(fd, file, on, &tu->fasync); + if (err < 0) + return err; + return 0; +} + +static ssize_t snd_timer_user_read(struct file *file, char __user *buffer, size_t count, loff_t *offset) +{ + snd_timer_user_t *tu; + long result = 0, unit; + int err = 0; + + tu = file->private_data; + unit = tu->tread ? sizeof(snd_timer_tread_t) : sizeof(snd_timer_read_t); + spin_lock_irq(&tu->qlock); + while ((long)count - result >= unit) { + while (!tu->qused) { + wait_queue_t wait; + + if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { + err = -EAGAIN; + break; + } + + set_current_state(TASK_INTERRUPTIBLE); + init_waitqueue_entry(&wait, current); + add_wait_queue(&tu->qchange_sleep, &wait); + + spin_unlock_irq(&tu->qlock); + schedule(); + spin_lock_irq(&tu->qlock); + + remove_wait_queue(&tu->qchange_sleep, &wait); + + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + + spin_unlock_irq(&tu->qlock); + if (err < 0) + goto _error; + + if (tu->tread) { + if (copy_to_user(buffer, &tu->tqueue[tu->qhead++], sizeof(snd_timer_tread_t))) { + err = -EFAULT; + goto _error; + } + } else { + if (copy_to_user(buffer, &tu->queue[tu->qhead++], sizeof(snd_timer_read_t))) { + err = -EFAULT; + goto _error; + } + } + + tu->qhead %= tu->queue_size; + + result += unit; + buffer += unit; + + spin_lock_irq(&tu->qlock); + tu->qused--; + } + spin_unlock_irq(&tu->qlock); + _error: + return result > 0 ? result : err; +} + +static unsigned int snd_timer_user_poll(struct file *file, poll_table * wait) +{ + unsigned int mask; + snd_timer_user_t *tu; + + tu = file->private_data; + + poll_wait(file, &tu->qchange_sleep, wait); + + mask = 0; + if (tu->qused) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +#ifdef CONFIG_COMPAT +#include "timer_compat.c" +#else +#define snd_timer_user_ioctl_compat NULL +#endif + +static struct file_operations snd_timer_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_timer_user_read, + .open = snd_timer_user_open, + .release = snd_timer_user_release, + .poll = snd_timer_user_poll, + .unlocked_ioctl = snd_timer_user_ioctl, + .compat_ioctl = snd_timer_user_ioctl_compat, + .fasync = snd_timer_user_fasync, +}; + +static snd_minor_t snd_timer_reg = +{ + .comment = "timer", + .f_ops = &snd_timer_f_ops, +}; + +/* + * ENTRY functions + */ + +static snd_info_entry_t *snd_timer_proc_entry = NULL; + +static int __init alsa_timer_init(void) +{ + int err; + snd_info_entry_t *entry; + +#ifdef SNDRV_OSS_INFO_DEV_TIMERS + snd_oss_info_register(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1, "system timer"); +#endif + if ((entry = snd_info_create_module_entry(THIS_MODULE, "timers", NULL)) != NULL) { + entry->c.text.read_size = SNDRV_TIMER_DEVICES * 128; + entry->c.text.read = snd_timer_proc_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_timer_proc_entry = entry; + if ((err = snd_timer_register_system()) < 0) + snd_printk(KERN_ERR "unable to register system timer (%i)\n", err); + if ((err = snd_register_device(SNDRV_DEVICE_TYPE_TIMER, + NULL, 0, &snd_timer_reg, "timer"))<0) + snd_printk(KERN_ERR "unable to register timer device (%i)\n", err); + return 0; +} + +static void __exit alsa_timer_exit(void) +{ + struct list_head *p, *n; + + snd_unregister_device(SNDRV_DEVICE_TYPE_TIMER, NULL, 0); + /* unregister the system timer */ + list_for_each_safe(p, n, &snd_timer_list) { + snd_timer_t *timer = (snd_timer_t *)list_entry(p, snd_timer_t, device_list); + snd_timer_unregister(timer); + } + if (snd_timer_proc_entry) { + snd_info_unregister(snd_timer_proc_entry); + snd_timer_proc_entry = NULL; + } +#ifdef SNDRV_OSS_INFO_DEV_TIMERS + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1); +#endif +} + +module_init(alsa_timer_init) +module_exit(alsa_timer_exit) + +EXPORT_SYMBOL(snd_timer_open); +EXPORT_SYMBOL(snd_timer_close); +EXPORT_SYMBOL(snd_timer_resolution); +EXPORT_SYMBOL(snd_timer_start); +EXPORT_SYMBOL(snd_timer_stop); +EXPORT_SYMBOL(snd_timer_continue); +EXPORT_SYMBOL(snd_timer_pause); +EXPORT_SYMBOL(snd_timer_new); +EXPORT_SYMBOL(snd_timer_notify); +EXPORT_SYMBOL(snd_timer_global_new); +EXPORT_SYMBOL(snd_timer_global_free); +EXPORT_SYMBOL(snd_timer_global_register); +EXPORT_SYMBOL(snd_timer_global_unregister); +EXPORT_SYMBOL(snd_timer_interrupt); +EXPORT_SYMBOL(snd_timer_system_resolution); diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c new file mode 100644 index 000000000000..9fbc3957a22d --- /dev/null +++ b/sound/core/timer_compat.c @@ -0,0 +1,119 @@ +/* + * 32bit -> 64bit ioctl wrapper for timer API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This file included from timer.c */ + +#include <linux/compat.h> + +struct sndrv_timer_info32 { + u32 flags; + s32 card; + unsigned char id[64]; + unsigned char name[80]; + u32 reserved0; + u32 resolution; + unsigned char reserved[64]; +}; + +static int snd_timer_user_info_compat(struct file *file, + struct sndrv_timer_info32 __user *_info) +{ + snd_timer_user_t *tu; + struct sndrv_timer_info32 info; + snd_timer_t *t; + + tu = file->private_data; + snd_assert(tu->timeri != NULL, return -ENXIO); + t = tu->timeri->timer; + snd_assert(t != NULL, return -ENXIO); + memset(&info, 0, sizeof(info)); + info.card = t->card ? t->card->number : -1; + if (t->hw.flags & SNDRV_TIMER_HW_SLAVE) + info.flags |= SNDRV_TIMER_FLG_SLAVE; + strlcpy(info.id, t->id, sizeof(info.id)); + strlcpy(info.name, t->name, sizeof(info.name)); + info.resolution = t->hw.resolution; + if (copy_to_user(_info, &info, sizeof(*_info))) + return -EFAULT; + return 0; +} + +struct sndrv_timer_status32 { + struct compat_timespec tstamp; + u32 resolution; + u32 lost; + u32 overrun; + u32 queue; + unsigned char reserved[64]; +}; + +static int snd_timer_user_status_compat(struct file *file, + struct sndrv_timer_status32 __user *_status) +{ + snd_timer_user_t *tu; + snd_timer_status_t status; + + tu = file->private_data; + snd_assert(tu->timeri != NULL, return -ENXIO); + memset(&status, 0, sizeof(status)); + status.tstamp = tu->tstamp; + status.resolution = snd_timer_resolution(tu->timeri); + status.lost = tu->timeri->lost; + status.overrun = tu->overrun; + spin_lock_irq(&tu->qlock); + status.queue = tu->qused; + spin_unlock_irq(&tu->qlock); + if (copy_to_user(_status, &status, sizeof(status))) + return -EFAULT; + return 0; +} + +/* + */ + +enum { + SNDRV_TIMER_IOCTL_INFO32 = _IOR('T', 0x11, struct sndrv_timer_info32), + SNDRV_TIMER_IOCTL_STATUS32 = _IOW('T', 0x14, struct sndrv_timer_status32), +}; + +static long snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = compat_ptr(arg); + + switch (cmd) { + case SNDRV_TIMER_IOCTL_PVERSION: + case SNDRV_TIMER_IOCTL_TREAD: + case SNDRV_TIMER_IOCTL_GINFO: + case SNDRV_TIMER_IOCTL_GPARAMS: + case SNDRV_TIMER_IOCTL_GSTATUS: + case SNDRV_TIMER_IOCTL_SELECT: + case SNDRV_TIMER_IOCTL_PARAMS: + case SNDRV_TIMER_IOCTL_START: + case SNDRV_TIMER_IOCTL_STOP: + case SNDRV_TIMER_IOCTL_CONTINUE: + case SNDRV_TIMER_IOCTL_NEXT_DEVICE: + return snd_timer_user_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_TIMER_IOCTL_INFO32: + return snd_timer_user_info_compat(file, argp); + case SNDRV_TIMER_IOCTL_STATUS32: + return snd_timer_user_status_compat(file, argp); + } + return -ENOIOCTLCMD; +} diff --git a/sound/core/wrappers.c b/sound/core/wrappers.c new file mode 100644 index 000000000000..9f393023c327 --- /dev/null +++ b/sound/core/wrappers.c @@ -0,0 +1,50 @@ +/* + * Various wrappers + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> + +#ifdef CONFIG_SND_DEBUG_MEMORY +void *snd_wrapper_kmalloc(size_t size, int flags) +{ + return kmalloc(size, flags); +} + +void snd_wrapper_kfree(const void *obj) +{ + kfree(obj); +} + +void *snd_wrapper_vmalloc(unsigned long size) +{ + return vmalloc(size); +} + +void snd_wrapper_vfree(void *obj) +{ + vfree(obj); +} +#endif + |