diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/core/seq/seq_ports.c |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'sound/core/seq/seq_ports.c')
-rw-r--r-- | sound/core/seq/seq_ports.c | 674 |
1 files changed, 674 insertions, 0 deletions
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; +} |