| /* | |
| * ALSA sequencer Ports | |
| * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> | |
| * Jaroslav Kysela <perex@perex.cz> | |
| * | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| * | |
| */ | |
| #include <sound/core.h> | |
| #include <linux/slab.h> | |
| #include <linux/module.h> | |
| #include "seq_system.h" | |
| #include "seq_ports.h" | |
| #include "seq_clientmgr.h" | |
| /* | |
| registration of client ports | |
| */ | |
| /* | |
| NOTE: the current implementation of the port structure as a linked list is | |
| not optimal for clients that have many ports. For sending messages to all | |
| subscribers of a port we first need to find the address of the port | |
| structure, which means we have to traverse the list. A direct access table | |
| (array) would be better, but big preallocated arrays waste memory. | |
| Possible actions: | |
| 1) leave it this way, a client does normaly does not have more than a few | |
| ports | |
| 2) replace the linked list of ports by a array of pointers which is | |
| dynamicly kmalloced. When a port is added or deleted we can simply allocate | |
| a new array, copy the corresponding pointers, and delete the old one. We | |
| then only need a pointer to this array, and an integer that tells us how | |
| much elements are in array. | |
| */ | |
| /* return pointer to port structure - port is locked if found */ | |
| struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, | |
| int num) | |
| { | |
| struct snd_seq_client_port *port; | |
| if (client == NULL) | |
| return NULL; | |
| read_lock(&client->ports_lock); | |
| list_for_each_entry(port, &client->ports_list_head, list) { | |
| if (port->addr.port == num) { | |
| if (port->closing) | |
| break; /* deleting now */ | |
| snd_use_lock_use(&port->use_lock); | |
| read_unlock(&client->ports_lock); | |
| return port; | |
| } | |
| } | |
| read_unlock(&client->ports_lock); | |
| return NULL; /* not found */ | |
| } | |
| /* search for the next port - port is locked if found */ | |
| struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client, | |
| struct snd_seq_port_info *pinfo) | |
| { | |
| int num; | |
| struct snd_seq_client_port *port, *found; | |
| num = pinfo->addr.port; | |
| found = NULL; | |
| read_lock(&client->ports_lock); | |
| list_for_each_entry(port, &client->ports_list_head, list) { | |
| if (port->addr.port < num) | |
| continue; | |
| if (port->addr.port == num) { | |
| found = port; | |
| break; | |
| } | |
| if (found == NULL || port->addr.port < found->addr.port) | |
| found = port; | |
| } | |
| if (found) { | |
| if (found->closing) | |
| found = NULL; | |
| else | |
| snd_use_lock_use(&found->use_lock); | |
| } | |
| read_unlock(&client->ports_lock); | |
| return found; | |
| } | |
| /* initialize snd_seq_port_subs_info */ | |
| static void port_subs_info_init(struct snd_seq_port_subs_info *grp) | |
| { | |
| INIT_LIST_HEAD(&grp->list_head); | |
| grp->count = 0; | |
| grp->exclusive = 0; | |
| rwlock_init(&grp->list_lock); | |
| init_rwsem(&grp->list_mutex); | |
| grp->open = NULL; | |
| grp->close = NULL; | |
| } | |
| /* create a port, port number is returned (-1 on failure) */ | |
| struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, | |
| int port) | |
| { | |
| unsigned long flags; | |
| struct snd_seq_client_port *new_port, *p; | |
| int num = -1; | |
| /* sanity check */ | |
| if (snd_BUG_ON(!client)) | |
| return NULL; | |
| if (client->num_ports >= SNDRV_SEQ_MAX_PORTS) { | |
| pr_warn("ALSA: seq: too many ports for client %d\n", client->number); | |
| return NULL; | |
| } | |
| /* create a new port */ | |
| new_port = kzalloc(sizeof(*new_port), GFP_KERNEL); | |
| if (!new_port) | |
| return NULL; /* failure, out of memory */ | |
| /* init port data */ | |
| new_port->addr.client = client->number; | |
| new_port->addr.port = -1; | |
| new_port->owner = THIS_MODULE; | |
| sprintf(new_port->name, "port-%d", num); | |
| snd_use_lock_init(&new_port->use_lock); | |
| port_subs_info_init(&new_port->c_src); | |
| port_subs_info_init(&new_port->c_dest); | |
| num = port >= 0 ? port : 0; | |
| mutex_lock(&client->ports_mutex); | |
| write_lock_irqsave(&client->ports_lock, flags); | |
| list_for_each_entry(p, &client->ports_list_head, list) { | |
| if (p->addr.port > num) | |
| break; | |
| if (port < 0) /* auto-probe mode */ | |
| num = p->addr.port + 1; | |
| } | |
| /* insert the new port */ | |
| list_add_tail(&new_port->list, &p->list); | |
| client->num_ports++; | |
| new_port->addr.port = num; /* store the port number in the port */ | |
| write_unlock_irqrestore(&client->ports_lock, flags); | |
| mutex_unlock(&client->ports_mutex); | |
| sprintf(new_port->name, "port-%d", num); | |
| return new_port; | |
| } | |
| /* */ | |
| static int subscribe_port(struct snd_seq_client *client, | |
| struct snd_seq_client_port *port, | |
| struct snd_seq_port_subs_info *grp, | |
| struct snd_seq_port_subscribe *info, int send_ack); | |
| static int unsubscribe_port(struct snd_seq_client *client, | |
| struct snd_seq_client_port *port, | |
| struct snd_seq_port_subs_info *grp, | |
| struct snd_seq_port_subscribe *info, int send_ack); | |
| static struct snd_seq_client_port *get_client_port(struct snd_seq_addr *addr, | |
| struct snd_seq_client **cp) | |
| { | |
| struct snd_seq_client_port *p; | |
| *cp = snd_seq_client_use_ptr(addr->client); | |
| if (*cp) { | |
| p = snd_seq_port_use_ptr(*cp, addr->port); | |
| if (! p) { | |
| snd_seq_client_unlock(*cp); | |
| *cp = NULL; | |
| } | |
| return p; | |
| } | |
| return NULL; | |
| } | |
| static void delete_and_unsubscribe_port(struct snd_seq_client *client, | |
| struct snd_seq_client_port *port, | |
| struct snd_seq_subscribers *subs, | |
| bool is_src, bool ack); | |
| static inline struct snd_seq_subscribers * | |
| get_subscriber(struct list_head *p, bool is_src) | |
| { | |
| if (is_src) | |
| return list_entry(p, struct snd_seq_subscribers, src_list); | |
| else | |
| return list_entry(p, struct snd_seq_subscribers, dest_list); | |
| } | |
| /* | |
| * remove all subscribers on the list | |
| * this is called from port_delete, for each src and dest list. | |
| */ | |
| static void clear_subscriber_list(struct snd_seq_client *client, | |
| struct snd_seq_client_port *port, | |
| struct snd_seq_port_subs_info *grp, | |
| int is_src) | |
| { | |
| struct list_head *p, *n; | |
| list_for_each_safe(p, n, &grp->list_head) { | |
| struct snd_seq_subscribers *subs; | |
| struct snd_seq_client *c; | |
| struct snd_seq_client_port *aport; | |
| subs = get_subscriber(p, is_src); | |
| if (is_src) | |
| aport = get_client_port(&subs->info.dest, &c); | |
| else | |
| aport = get_client_port(&subs->info.sender, &c); | |
| delete_and_unsubscribe_port(client, port, subs, is_src, false); | |
| 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); | |
| continue; | |
| } | |
| /* ok we got the connected port */ | |
| delete_and_unsubscribe_port(c, aport, subs, !is_src, true); | |
| kfree(subs); | |
| snd_seq_port_unlock(aport); | |
| snd_seq_client_unlock(c); | |
| } | |
| } | |
| /* delete port data */ | |
| static int port_delete(struct snd_seq_client *client, | |
| struct snd_seq_client_port *port) | |
| { | |
| /* set closing flag and wait for all port access are gone */ | |
| port->closing = 1; | |
| snd_use_lock_sync(&port->use_lock); | |
| /* clear subscribers info */ | |
| clear_subscriber_list(client, port, &port->c_src, true); | |
| clear_subscriber_list(client, port, &port->c_dest, false); | |
| if (port->private_free) | |
| port->private_free(port->private_data); | |
| snd_BUG_ON(port->c_src.count != 0); | |
| snd_BUG_ON(port->c_dest.count != 0); | |
| kfree(port); | |
| return 0; | |
| } | |
| /* delete a port with the given port id */ | |
| int snd_seq_delete_port(struct snd_seq_client *client, int port) | |
| { | |
| unsigned long flags; | |
| struct snd_seq_client_port *found = NULL, *p; | |
| mutex_lock(&client->ports_mutex); | |
| write_lock_irqsave(&client->ports_lock, flags); | |
| list_for_each_entry(p, &client->ports_list_head, list) { | |
| if (p->addr.port == port) { | |
| /* ok found. delete from the list at first */ | |
| list_del(&p->list); | |
| client->num_ports--; | |
| found = p; | |
| break; | |
| } | |
| } | |
| write_unlock_irqrestore(&client->ports_lock, flags); | |
| mutex_unlock(&client->ports_mutex); | |
| if (found) | |
| return port_delete(client, found); | |
| else | |
| return -ENOENT; | |
| } | |
| /* delete the all ports belonging to the given client */ | |
| int snd_seq_delete_all_ports(struct snd_seq_client *client) | |
| { | |
| unsigned long flags; | |
| struct list_head deleted_list; | |
| struct snd_seq_client_port *port, *tmp; | |
| /* move the port list to deleted_list, and | |
| * clear the port list in the client data. | |
| */ | |
| mutex_lock(&client->ports_mutex); | |
| write_lock_irqsave(&client->ports_lock, flags); | |
| if (! list_empty(&client->ports_list_head)) { | |
| list_add(&deleted_list, &client->ports_list_head); | |
| list_del_init(&client->ports_list_head); | |
| } else { | |
| INIT_LIST_HEAD(&deleted_list); | |
| } | |
| client->num_ports = 0; | |
| write_unlock_irqrestore(&client->ports_lock, flags); | |
| /* remove each port in deleted_list */ | |
| list_for_each_entry_safe(port, tmp, &deleted_list, list) { | |
| list_del(&port->list); | |
| snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); | |
| port_delete(client, port); | |
| } | |
| mutex_unlock(&client->ports_mutex); | |
| return 0; | |
| } | |
| /* set port info fields */ | |
| int snd_seq_set_port_info(struct snd_seq_client_port * port, | |
| struct snd_seq_port_info * info) | |
| { | |
| if (snd_BUG_ON(!port || !info)) | |
| return -EINVAL; | |
| /* set port name */ | |
| if (info->name[0]) | |
| strlcpy(port->name, info->name, sizeof(port->name)); | |
| /* set capabilities */ | |
| port->capability = info->capability; | |
| /* get port type */ | |
| port->type = info->type; | |
| /* information about supported channels/voices */ | |
| port->midi_channels = info->midi_channels; | |
| port->midi_voices = info->midi_voices; | |
| port->synth_voices = info->synth_voices; | |
| /* timestamping */ | |
| port->timestamping = (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0; | |
| port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0; | |
| port->time_queue = info->time_queue; | |
| return 0; | |
| } | |
| /* get port info fields */ | |
| int snd_seq_get_port_info(struct snd_seq_client_port * port, | |
| struct snd_seq_port_info * info) | |
| { | |
| if (snd_BUG_ON(!port || !info)) | |
| return -EINVAL; | |
| /* get port name */ | |
| strlcpy(info->name, port->name, sizeof(info->name)); | |
| /* get capabilities */ | |
| info->capability = port->capability; | |
| /* get port type */ | |
| info->type = port->type; | |
| /* information about supported channels/voices */ | |
| info->midi_channels = port->midi_channels; | |
| info->midi_voices = port->midi_voices; | |
| info->synth_voices = port->synth_voices; | |
| /* get subscriber counts */ | |
| info->read_use = port->c_src.count; | |
| info->write_use = port->c_dest.count; | |
| /* timestamping */ | |
| info->flags = 0; | |
| if (port->timestamping) { | |
| info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP; | |
| if (port->time_real) | |
| info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL; | |
| info->time_queue = port->time_queue; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * call callback functions (if any): | |
| * the callbacks are invoked only when the first (for connection) or | |
| * the last subscription (for disconnection) is done. Second or later | |
| * subscription results in increment of counter, but no callback is | |
| * invoked. | |
| * This feature is useful if these callbacks are associated with | |
| * initialization or termination of devices (see seq_midi.c). | |
| */ | |
| static int subscribe_port(struct snd_seq_client *client, | |
| struct snd_seq_client_port *port, | |
| struct snd_seq_port_subs_info *grp, | |
| struct snd_seq_port_subscribe *info, | |
| int send_ack) | |
| { | |
| int err = 0; | |
| if (!try_module_get(port->owner)) | |
| return -EFAULT; | |
| grp->count++; | |
| if (grp->open && grp->count == 1) { | |
| err = grp->open(port->private_data, info); | |
| if (err < 0) { | |
| module_put(port->owner); | |
| grp->count--; | |
| } | |
| } | |
| if (err >= 0 && send_ack && client->type == USER_CLIENT) | |
| snd_seq_client_notify_subscription(port->addr.client, port->addr.port, | |
| info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED); | |
| return err; | |
| } | |
| static int unsubscribe_port(struct snd_seq_client *client, | |
| struct snd_seq_client_port *port, | |
| struct snd_seq_port_subs_info *grp, | |
| struct snd_seq_port_subscribe *info, | |
| int send_ack) | |
| { | |
| int err = 0; | |
| if (! grp->count) | |
| return -EINVAL; | |
| grp->count--; | |
| if (grp->close && grp->count == 0) | |
| err = grp->close(port->private_data, info); | |
| if (send_ack && client->type == USER_CLIENT) | |
| snd_seq_client_notify_subscription(port->addr.client, port->addr.port, | |
| info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED); | |
| module_put(port->owner); | |
| return err; | |
| } | |
| /* check if both addresses are identical */ | |
| static inline int addr_match(struct snd_seq_addr *r, struct snd_seq_addr *s) | |
| { | |
| return (r->client == s->client) && (r->port == s->port); | |
| } | |
| /* check the two subscribe info match */ | |
| /* if flags is zero, checks only sender and destination addresses */ | |
| static int match_subs_info(struct snd_seq_port_subscribe *r, | |
| struct snd_seq_port_subscribe *s) | |
| { | |
| if (addr_match(&r->sender, &s->sender) && | |
| addr_match(&r->dest, &s->dest)) { | |
| if (r->flags && r->flags == s->flags) | |
| return r->queue == s->queue; | |
| else if (! r->flags) | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| static int check_and_subscribe_port(struct snd_seq_client *client, | |
| struct snd_seq_client_port *port, | |
| struct snd_seq_subscribers *subs, | |
| bool is_src, bool exclusive, bool ack) | |
| { | |
| struct snd_seq_port_subs_info *grp; | |
| struct list_head *p; | |
| struct snd_seq_subscribers *s; | |
| int err; | |
| grp = is_src ? &port->c_src : &port->c_dest; | |
| err = -EBUSY; | |
| down_write(&grp->list_mutex); | |
| if (exclusive) { | |
| if (!list_empty(&grp->list_head)) | |
| goto __error; | |
| } else { | |
| if (grp->exclusive) | |
| goto __error; | |
| /* check whether already exists */ | |
| list_for_each(p, &grp->list_head) { | |
| s = get_subscriber(p, is_src); | |
| if (match_subs_info(&subs->info, &s->info)) | |
| goto __error; | |
| } | |
| } | |
| err = subscribe_port(client, port, grp, &subs->info, ack); | |
| if (err < 0) { | |
| grp->exclusive = 0; | |
| goto __error; | |
| } | |
| /* add to list */ | |
| write_lock_irq(&grp->list_lock); | |
| if (is_src) | |
| list_add_tail(&subs->src_list, &grp->list_head); | |
| else | |
| list_add_tail(&subs->dest_list, &grp->list_head); | |
| grp->exclusive = exclusive; | |
| atomic_inc(&subs->ref_count); | |
| write_unlock_irq(&grp->list_lock); | |
| err = 0; | |
| __error: | |
| up_write(&grp->list_mutex); | |
| return err; | |
| } | |
| static void delete_and_unsubscribe_port(struct snd_seq_client *client, | |
| struct snd_seq_client_port *port, | |
| struct snd_seq_subscribers *subs, | |
| bool is_src, bool ack) | |
| { | |
| struct snd_seq_port_subs_info *grp; | |
| struct list_head *list; | |
| bool empty; | |
| grp = is_src ? &port->c_src : &port->c_dest; | |
| list = is_src ? &subs->src_list : &subs->dest_list; | |
| down_write(&grp->list_mutex); | |
| write_lock_irq(&grp->list_lock); | |
| empty = list_empty(list); | |
| if (!empty) | |
| list_del_init(list); | |
| grp->exclusive = 0; | |
| write_unlock_irq(&grp->list_lock); | |
| up_write(&grp->list_mutex); | |
| if (!empty) | |
| unsubscribe_port(client, port, grp, &subs->info, ack); | |
| } | |
| /* connect two ports */ | |
| int snd_seq_port_connect(struct snd_seq_client *connector, | |
| struct snd_seq_client *src_client, | |
| struct snd_seq_client_port *src_port, | |
| struct snd_seq_client *dest_client, | |
| struct snd_seq_client_port *dest_port, | |
| struct snd_seq_port_subscribe *info) | |
| { | |
| struct snd_seq_subscribers *subs; | |
| bool exclusive; | |
| int err; | |
| subs = kzalloc(sizeof(*subs), GFP_KERNEL); | |
| if (!subs) | |
| return -ENOMEM; | |
| subs->info = *info; | |
| atomic_set(&subs->ref_count, 0); | |
| INIT_LIST_HEAD(&subs->src_list); | |
| INIT_LIST_HEAD(&subs->dest_list); | |
| exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE); | |
| err = check_and_subscribe_port(src_client, src_port, subs, true, | |
| exclusive, | |
| connector->number != src_client->number); | |
| if (err < 0) | |
| goto error; | |
| err = check_and_subscribe_port(dest_client, dest_port, subs, false, | |
| exclusive, | |
| connector->number != dest_client->number); | |
| if (err < 0) | |
| goto error_dest; | |
| return 0; | |
| error_dest: | |
| delete_and_unsubscribe_port(src_client, src_port, subs, true, | |
| connector->number != src_client->number); | |
| error: | |
| kfree(subs); | |
| return err; | |
| } | |
| /* remove the connection */ | |
| int snd_seq_port_disconnect(struct snd_seq_client *connector, | |
| struct snd_seq_client *src_client, | |
| struct snd_seq_client_port *src_port, | |
| struct snd_seq_client *dest_client, | |
| struct snd_seq_client_port *dest_port, | |
| struct snd_seq_port_subscribe *info) | |
| { | |
| struct snd_seq_port_subs_info *src = &src_port->c_src; | |
| struct snd_seq_subscribers *subs; | |
| int err = -ENOENT; | |
| down_write(&src->list_mutex); | |
| /* look for the connection */ | |
| list_for_each_entry(subs, &src->list_head, src_list) { | |
| if (match_subs_info(info, &subs->info)) { | |
| atomic_dec(&subs->ref_count); /* mark as not ready */ | |
| err = 0; | |
| break; | |
| } | |
| } | |
| up_write(&src->list_mutex); | |
| if (err < 0) | |
| return err; | |
| delete_and_unsubscribe_port(src_client, src_port, subs, true, | |
| connector->number != src_client->number); | |
| delete_and_unsubscribe_port(dest_client, dest_port, subs, false, | |
| connector->number != dest_client->number); | |
| kfree(subs); | |
| return 0; | |
| } | |
| /* get matched subscriber */ | |
| struct snd_seq_subscribers *snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, | |
| struct snd_seq_addr *dest_addr) | |
| { | |
| struct snd_seq_subscribers *s, *found = NULL; | |
| down_read(&src_grp->list_mutex); | |
| list_for_each_entry(s, &src_grp->list_head, src_list) { | |
| if (addr_match(dest_addr, &s->info.dest)) { | |
| found = s; | |
| break; | |
| } | |
| } | |
| up_read(&src_grp->list_mutex); | |
| return found; | |
| } | |
| /* | |
| * Attach a device driver that wants to receive events from the | |
| * sequencer. Returns the new port number on success. | |
| * A driver that wants to receive the events converted to midi, will | |
| * use snd_seq_midisynth_register_port(). | |
| */ | |
| /* exported */ | |
| int snd_seq_event_port_attach(int client, | |
| struct snd_seq_port_callback *pcbp, | |
| int cap, int type, int midi_channels, | |
| int midi_voices, char *portname) | |
| { | |
| struct snd_seq_port_info portinfo; | |
| int ret; | |
| /* Set up the port */ | |
| memset(&portinfo, 0, sizeof(portinfo)); | |
| portinfo.addr.client = client; | |
| strlcpy(portinfo.name, portname ? portname : "Unamed port", | |
| sizeof(portinfo.name)); | |
| portinfo.capability = cap; | |
| portinfo.type = type; | |
| portinfo.kernel = pcbp; | |
| portinfo.midi_channels = midi_channels; | |
| portinfo.midi_voices = midi_voices; | |
| /* Create it */ | |
| ret = snd_seq_kernel_client_ctl(client, | |
| SNDRV_SEQ_IOCTL_CREATE_PORT, | |
| &portinfo); | |
| if (ret >= 0) | |
| ret = portinfo.addr.port; | |
| return ret; | |
| } | |
| EXPORT_SYMBOL(snd_seq_event_port_attach); | |
| /* | |
| * Detach the driver from a port. | |
| */ | |
| /* exported */ | |
| int snd_seq_event_port_detach(int client, int port) | |
| { | |
| struct snd_seq_port_info portinfo; | |
| int err; | |
| memset(&portinfo, 0, sizeof(portinfo)); | |
| portinfo.addr.client = client; | |
| portinfo.addr.port = port; | |
| err = snd_seq_kernel_client_ctl(client, | |
| SNDRV_SEQ_IOCTL_DELETE_PORT, | |
| &portinfo); | |
| return err; | |
| } | |
| EXPORT_SYMBOL(snd_seq_event_port_detach); |