|  | /* | 
|  | *   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); | 
|  | * the caller needs to unref the port via snd_seq_port_unlock() appropriately | 
|  | */ | 
|  | 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); | 
|  | snd_use_lock_use(&new_port->use_lock); | 
|  |  | 
|  | 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 */ | 
|  | sprintf(new_port->name, "port-%d", num); | 
|  | write_unlock_irqrestore(&client->ports_lock, flags); | 
|  | mutex_unlock(&client->ports_mutex); | 
|  |  | 
|  | 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); |