diff options
Diffstat (limited to 'sound/core/seq/seq_instr.c')
-rw-r--r-- | sound/core/seq/seq_instr.c | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/sound/core/seq/seq_instr.c b/sound/core/seq/seq_instr.c new file mode 100644 index 00000000000..5b40ea2ba8f --- /dev/null +++ b/sound/core/seq/seq_instr.c @@ -0,0 +1,653 @@ +/* + * Generic Instrument routines for ALSA sequencer + * Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "seq_clientmgr.h" +#include <sound/seq_instr.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer instrument library."); +MODULE_LICENSE("GPL"); + + +static void snd_instr_lock_ops(snd_seq_kinstr_list_t *list) +{ + if (!(list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT)) { + spin_lock_irqsave(&list->ops_lock, list->ops_flags); + } else { + down(&list->ops_mutex); + } +} + +static void snd_instr_unlock_ops(snd_seq_kinstr_list_t *list) +{ + if (!(list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT)) { + spin_unlock_irqrestore(&list->ops_lock, list->ops_flags); + } else { + up(&list->ops_mutex); + } +} + +static snd_seq_kinstr_t *snd_seq_instr_new(int add_len, int atomic) +{ + snd_seq_kinstr_t *instr; + + instr = kcalloc(1, sizeof(snd_seq_kinstr_t) + add_len, atomic ? GFP_ATOMIC : GFP_KERNEL); + if (instr == NULL) + return NULL; + instr->add_len = add_len; + return instr; +} + +static int snd_seq_instr_free(snd_seq_kinstr_t *instr, int atomic) +{ + int result = 0; + + if (instr == NULL) + return -EINVAL; + if (instr->ops && instr->ops->remove) + result = instr->ops->remove(instr->ops->private_data, instr, 1); + if (!result) + kfree(instr); + return result; +} + +snd_seq_kinstr_list_t *snd_seq_instr_list_new(void) +{ + snd_seq_kinstr_list_t *list; + + list = kcalloc(1, sizeof(snd_seq_kinstr_list_t), GFP_KERNEL); + if (list == NULL) + return NULL; + spin_lock_init(&list->lock); + spin_lock_init(&list->ops_lock); + init_MUTEX(&list->ops_mutex); + list->owner = -1; + return list; +} + +void snd_seq_instr_list_free(snd_seq_kinstr_list_t **list_ptr) +{ + snd_seq_kinstr_list_t *list; + snd_seq_kinstr_t *instr; + snd_seq_kcluster_t *cluster; + int idx; + unsigned long flags; + + if (list_ptr == NULL) + return; + list = *list_ptr; + *list_ptr = NULL; + if (list == NULL) + return; + + for (idx = 0; idx < SNDRV_SEQ_INSTR_HASH_SIZE; idx++) { + while ((instr = list->hash[idx]) != NULL) { + list->hash[idx] = instr->next; + list->count--; + spin_lock_irqsave(&list->lock, flags); + while (instr->use) { + spin_unlock_irqrestore(&list->lock, flags); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + spin_lock_irqsave(&list->lock, flags); + } + spin_unlock_irqrestore(&list->lock, flags); + if (snd_seq_instr_free(instr, 0)<0) + snd_printk(KERN_WARNING "instrument free problem\n"); + } + while ((cluster = list->chash[idx]) != NULL) { + list->chash[idx] = cluster->next; + list->ccount--; + kfree(cluster); + } + } + kfree(list); +} + +static int instr_free_compare(snd_seq_kinstr_t *instr, + snd_seq_instr_header_t *ifree, + unsigned int client) +{ + switch (ifree->cmd) { + case SNDRV_SEQ_INSTR_FREE_CMD_ALL: + /* all, except private for other clients */ + if ((instr->instr.std & 0xff000000) == 0) + return 0; + if (((instr->instr.std >> 24) & 0xff) == client) + return 0; + return 1; + case SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE: + /* all my private instruments */ + if ((instr->instr.std & 0xff000000) == 0) + return 1; + if (((instr->instr.std >> 24) & 0xff) == client) + return 0; + return 1; + case SNDRV_SEQ_INSTR_FREE_CMD_CLUSTER: + /* all my private instruments */ + if ((instr->instr.std & 0xff000000) == 0) { + if (instr->instr.cluster == ifree->id.cluster) + return 0; + return 1; + } + if (((instr->instr.std >> 24) & 0xff) == client) { + if (instr->instr.cluster == ifree->id.cluster) + return 0; + } + return 1; + } + return 1; +} + +int snd_seq_instr_list_free_cond(snd_seq_kinstr_list_t *list, + snd_seq_instr_header_t *ifree, + int client, + int atomic) +{ + snd_seq_kinstr_t *instr, *prev, *next, *flist; + int idx; + unsigned long flags; + + snd_instr_lock_ops(list); + for (idx = 0; idx < SNDRV_SEQ_INSTR_HASH_SIZE; idx++) { + spin_lock_irqsave(&list->lock, flags); + instr = list->hash[idx]; + prev = flist = NULL; + while (instr) { + while (instr && instr_free_compare(instr, ifree, (unsigned int)client)) { + prev = instr; + instr = instr->next; + } + if (instr == NULL) + continue; + if (instr->ops && instr->ops->notify) + instr->ops->notify(instr->ops->private_data, instr, SNDRV_SEQ_INSTR_NOTIFY_REMOVE); + next = instr->next; + if (prev == NULL) { + list->hash[idx] = next; + } else { + prev->next = next; + } + list->count--; + instr->next = flist; + flist = instr; + instr = next; + } + spin_unlock_irqrestore(&list->lock, flags); + while (flist) { + instr = flist; + flist = instr->next; + while (instr->use) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + } + if (snd_seq_instr_free(instr, atomic)<0) + snd_printk(KERN_WARNING "instrument free problem\n"); + instr = next; + } + } + snd_instr_unlock_ops(list); + return 0; +} + +static int compute_hash_instr_key(snd_seq_instr_t *instr) +{ + int result; + + result = instr->bank | (instr->prg << 16); + result += result >> 24; + result += result >> 16; + result += result >> 8; + return result & (SNDRV_SEQ_INSTR_HASH_SIZE-1); +} + +#if 0 +static int compute_hash_cluster_key(snd_seq_instr_cluster_t cluster) +{ + int result; + + result = cluster; + result += result >> 24; + result += result >> 16; + result += result >> 8; + return result & (SNDRV_SEQ_INSTR_HASH_SIZE-1); +} +#endif + +static int compare_instr(snd_seq_instr_t *i1, snd_seq_instr_t *i2, int exact) +{ + if (exact) { + if (i1->cluster != i2->cluster || + i1->bank != i2->bank || + i1->prg != i2->prg) + return 1; + if ((i1->std & 0xff000000) != (i2->std & 0xff000000)) + return 1; + if (!(i1->std & i2->std)) + return 1; + return 0; + } else { + unsigned int client_check; + + if (i2->cluster && i1->cluster != i2->cluster) + return 1; + client_check = i2->std & 0xff000000; + if (client_check) { + if ((i1->std & 0xff000000) != client_check) + return 1; + } else { + if ((i1->std & i2->std) != i2->std) + return 1; + } + return i1->bank != i2->bank || i1->prg != i2->prg; + } +} + +snd_seq_kinstr_t *snd_seq_instr_find(snd_seq_kinstr_list_t *list, + snd_seq_instr_t *instr, + int exact, + int follow_alias) +{ + unsigned long flags; + int depth = 0; + snd_seq_kinstr_t *result; + + if (list == NULL || instr == NULL) + return NULL; + spin_lock_irqsave(&list->lock, flags); + __again: + result = list->hash[compute_hash_instr_key(instr)]; + while (result) { + if (!compare_instr(&result->instr, instr, exact)) { + if (follow_alias && (result->type == SNDRV_SEQ_INSTR_ATYPE_ALIAS)) { + instr = (snd_seq_instr_t *)KINSTR_DATA(result); + if (++depth > 10) + goto __not_found; + goto __again; + } + result->use++; + spin_unlock_irqrestore(&list->lock, flags); + return result; + } + result = result->next; + } + __not_found: + spin_unlock_irqrestore(&list->lock, flags); + return NULL; +} + +void snd_seq_instr_free_use(snd_seq_kinstr_list_t *list, + snd_seq_kinstr_t *instr) +{ + unsigned long flags; + + if (list == NULL || instr == NULL) + return; + spin_lock_irqsave(&list->lock, flags); + if (instr->use <= 0) { + snd_printk(KERN_ERR "free_use: fatal!!! use = %i, name = '%s'\n", instr->use, instr->name); + } else { + instr->use--; + } + spin_unlock_irqrestore(&list->lock, flags); +} + +static snd_seq_kinstr_ops_t *instr_ops(snd_seq_kinstr_ops_t *ops, char *instr_type) +{ + while (ops) { + if (!strcmp(ops->instr_type, instr_type)) + return ops; + ops = ops->next; + } + return NULL; +} + +static int instr_result(snd_seq_event_t *ev, + int type, int result, + int atomic) +{ + snd_seq_event_t sev; + + memset(&sev, 0, sizeof(sev)); + sev.type = SNDRV_SEQ_EVENT_RESULT; + sev.flags = SNDRV_SEQ_TIME_STAMP_REAL | SNDRV_SEQ_EVENT_LENGTH_FIXED | + SNDRV_SEQ_PRIORITY_NORMAL; + sev.source = ev->dest; + sev.dest = ev->source; + sev.data.result.event = type; + sev.data.result.result = result; +#if 0 + printk("instr result - type = %i, result = %i, queue = %i, source.client:port = %i:%i, dest.client:port = %i:%i\n", + type, result, + sev.queue, + sev.source.client, sev.source.port, + sev.dest.client, sev.dest.port); +#endif + return snd_seq_kernel_client_dispatch(sev.source.client, &sev, atomic, 0); +} + +static int instr_begin(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + unsigned long flags; + + spin_lock_irqsave(&list->lock, flags); + if (list->owner >= 0 && list->owner != ev->source.client) { + spin_unlock_irqrestore(&list->lock, flags); + return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_BEGIN, -EBUSY, atomic); + } + list->owner = ev->source.client; + spin_unlock_irqrestore(&list->lock, flags); + return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_BEGIN, 0, atomic); +} + +static int instr_end(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + unsigned long flags; + + /* TODO: timeout handling */ + spin_lock_irqsave(&list->lock, flags); + if (list->owner == ev->source.client) { + list->owner = -1; + spin_unlock_irqrestore(&list->lock, flags); + return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_END, 0, atomic); + } + spin_unlock_irqrestore(&list->lock, flags); + return instr_result(ev, SNDRV_SEQ_EVENT_INSTR_END, -EINVAL, atomic); +} + +static int instr_info(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_format_info(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_reset(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_status(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_put(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + unsigned long flags; + snd_seq_instr_header_t put; + snd_seq_kinstr_t *instr; + int result = -EINVAL, len, key; + + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARUSR) + goto __return; + + if (ev->data.ext.len < sizeof(snd_seq_instr_header_t)) + goto __return; + if (copy_from_user(&put, (void __user *)ev->data.ext.ptr, sizeof(snd_seq_instr_header_t))) { + result = -EFAULT; + goto __return; + } + snd_instr_lock_ops(list); + if (put.id.instr.std & 0xff000000) { /* private instrument */ + put.id.instr.std &= 0x00ffffff; + put.id.instr.std |= (unsigned int)ev->source.client << 24; + } + if ((instr = snd_seq_instr_find(list, &put.id.instr, 1, 0))) { + snd_seq_instr_free_use(list, instr); + snd_instr_unlock_ops(list); + result = -EBUSY; + goto __return; + } + ops = instr_ops(ops, put.data.data.format); + if (ops == NULL) { + snd_instr_unlock_ops(list); + goto __return; + } + len = ops->add_len; + if (put.data.type == SNDRV_SEQ_INSTR_ATYPE_ALIAS) + len = sizeof(snd_seq_instr_t); + instr = snd_seq_instr_new(len, atomic); + if (instr == NULL) { + snd_instr_unlock_ops(list); + result = -ENOMEM; + goto __return; + } + instr->ops = ops; + instr->instr = put.id.instr; + strlcpy(instr->name, put.data.name, sizeof(instr->name)); + instr->type = put.data.type; + if (instr->type == SNDRV_SEQ_INSTR_ATYPE_DATA) { + result = ops->put(ops->private_data, + instr, + (void __user *)ev->data.ext.ptr + sizeof(snd_seq_instr_header_t), + ev->data.ext.len - sizeof(snd_seq_instr_header_t), + atomic, + put.cmd); + if (result < 0) { + snd_seq_instr_free(instr, atomic); + snd_instr_unlock_ops(list); + goto __return; + } + } + key = compute_hash_instr_key(&instr->instr); + spin_lock_irqsave(&list->lock, flags); + instr->next = list->hash[key]; + list->hash[key] = instr; + list->count++; + spin_unlock_irqrestore(&list->lock, flags); + snd_instr_unlock_ops(list); + result = 0; + __return: + instr_result(ev, SNDRV_SEQ_EVENT_INSTR_PUT, result, atomic); + return result; +} + +static int instr_get(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_free(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + snd_seq_instr_header_t ifree; + snd_seq_kinstr_t *instr, *prev; + int result = -EINVAL; + unsigned long flags; + unsigned int hash; + + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARUSR) + goto __return; + + if (ev->data.ext.len < sizeof(snd_seq_instr_header_t)) + goto __return; + if (copy_from_user(&ifree, (void __user *)ev->data.ext.ptr, sizeof(snd_seq_instr_header_t))) { + result = -EFAULT; + goto __return; + } + if (ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_ALL || + ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE || + ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_CLUSTER) { + result = snd_seq_instr_list_free_cond(list, &ifree, ev->dest.client, atomic); + goto __return; + } + if (ifree.cmd == SNDRV_SEQ_INSTR_FREE_CMD_SINGLE) { + if (ifree.id.instr.std & 0xff000000) { + ifree.id.instr.std &= 0x00ffffff; + ifree.id.instr.std |= (unsigned int)ev->source.client << 24; + } + hash = compute_hash_instr_key(&ifree.id.instr); + snd_instr_lock_ops(list); + spin_lock_irqsave(&list->lock, flags); + instr = list->hash[hash]; + prev = NULL; + while (instr) { + if (!compare_instr(&instr->instr, &ifree.id.instr, 1)) + goto __free_single; + prev = instr; + instr = instr->next; + } + result = -ENOENT; + spin_unlock_irqrestore(&list->lock, flags); + snd_instr_unlock_ops(list); + goto __return; + + __free_single: + if (prev) { + prev->next = instr->next; + } else { + list->hash[hash] = instr->next; + } + if (instr->ops && instr->ops->notify) + instr->ops->notify(instr->ops->private_data, instr, SNDRV_SEQ_INSTR_NOTIFY_REMOVE); + while (instr->use) { + spin_unlock_irqrestore(&list->lock, flags); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + spin_lock_irqsave(&list->lock, flags); + } + spin_unlock_irqrestore(&list->lock, flags); + result = snd_seq_instr_free(instr, atomic); + snd_instr_unlock_ops(list); + goto __return; + } + + __return: + instr_result(ev, SNDRV_SEQ_EVENT_INSTR_FREE, result, atomic); + return result; +} + +static int instr_list(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +static int instr_cluster(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int atomic, int hop) +{ + return -ENXIO; +} + +int snd_seq_instr_event(snd_seq_kinstr_ops_t *ops, + snd_seq_kinstr_list_t *list, + snd_seq_event_t *ev, + int client, + int atomic, + int hop) +{ + int direct = 0; + + snd_assert(ops != NULL && list != NULL && ev != NULL, return -EINVAL); + if (snd_seq_ev_is_direct(ev)) { + direct = 1; + switch (ev->type) { + case SNDRV_SEQ_EVENT_INSTR_BEGIN: + return instr_begin(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_END: + return instr_end(ops, list, ev, atomic, hop); + } + } + if ((list->flags & SNDRV_SEQ_INSTR_FLG_DIRECT) && !direct) + return -EINVAL; + switch (ev->type) { + case SNDRV_SEQ_EVENT_INSTR_INFO: + return instr_info(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_FINFO: + return instr_format_info(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_RESET: + return instr_reset(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_STATUS: + return instr_status(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_PUT: + return instr_put(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_GET: + return instr_get(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_FREE: + return instr_free(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_LIST: + return instr_list(ops, list, ev, atomic, hop); + case SNDRV_SEQ_EVENT_INSTR_CLUSTER: + return instr_cluster(ops, list, ev, atomic, hop); + } + return -EINVAL; +} + +/* + * Init part + */ + +static int __init alsa_seq_instr_init(void) +{ + return 0; +} + +static void __exit alsa_seq_instr_exit(void) +{ +} + +module_init(alsa_seq_instr_init) +module_exit(alsa_seq_instr_exit) + +EXPORT_SYMBOL(snd_seq_instr_list_new); +EXPORT_SYMBOL(snd_seq_instr_list_free); +EXPORT_SYMBOL(snd_seq_instr_list_free_cond); +EXPORT_SYMBOL(snd_seq_instr_find); +EXPORT_SYMBOL(snd_seq_instr_free_use); +EXPORT_SYMBOL(snd_seq_instr_event); |