cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

seq_ports.c (18790B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 *   ALSA sequencer Ports
      4 *   Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
      5 *                         Jaroslav Kysela <perex@perex.cz>
      6 */
      7
      8#include <sound/core.h>
      9#include <linux/slab.h>
     10#include <linux/module.h>
     11#include "seq_system.h"
     12#include "seq_ports.h"
     13#include "seq_clientmgr.h"
     14
     15/*
     16
     17   registration of client ports
     18
     19 */
     20
     21
     22/* 
     23
     24NOTE: the current implementation of the port structure as a linked list is
     25not optimal for clients that have many ports. For sending messages to all
     26subscribers of a port we first need to find the address of the port
     27structure, which means we have to traverse the list. A direct access table
     28(array) would be better, but big preallocated arrays waste memory.
     29
     30Possible actions:
     31
     321) leave it this way, a client does normaly does not have more than a few
     33ports
     34
     352) replace the linked list of ports by a array of pointers which is
     36dynamicly kmalloced. When a port is added or deleted we can simply allocate
     37a new array, copy the corresponding pointers, and delete the old one. We
     38then only need a pointer to this array, and an integer that tells us how
     39much elements are in array.
     40
     41*/
     42
     43/* return pointer to port structure - port is locked if found */
     44struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client,
     45						 int num)
     46{
     47	struct snd_seq_client_port *port;
     48
     49	if (client == NULL)
     50		return NULL;
     51	read_lock(&client->ports_lock);
     52	list_for_each_entry(port, &client->ports_list_head, list) {
     53		if (port->addr.port == num) {
     54			if (port->closing)
     55				break; /* deleting now */
     56			snd_use_lock_use(&port->use_lock);
     57			read_unlock(&client->ports_lock);
     58			return port;
     59		}
     60	}
     61	read_unlock(&client->ports_lock);
     62	return NULL;		/* not found */
     63}
     64
     65
     66/* search for the next port - port is locked if found */
     67struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client,
     68						       struct snd_seq_port_info *pinfo)
     69{
     70	int num;
     71	struct snd_seq_client_port *port, *found;
     72
     73	num = pinfo->addr.port;
     74	found = NULL;
     75	read_lock(&client->ports_lock);
     76	list_for_each_entry(port, &client->ports_list_head, list) {
     77		if (port->addr.port < num)
     78			continue;
     79		if (port->addr.port == num) {
     80			found = port;
     81			break;
     82		}
     83		if (found == NULL || port->addr.port < found->addr.port)
     84			found = port;
     85	}
     86	if (found) {
     87		if (found->closing)
     88			found = NULL;
     89		else
     90			snd_use_lock_use(&found->use_lock);
     91	}
     92	read_unlock(&client->ports_lock);
     93	return found;
     94}
     95
     96
     97/* initialize snd_seq_port_subs_info */
     98static void port_subs_info_init(struct snd_seq_port_subs_info *grp)
     99{
    100	INIT_LIST_HEAD(&grp->list_head);
    101	grp->count = 0;
    102	grp->exclusive = 0;
    103	rwlock_init(&grp->list_lock);
    104	init_rwsem(&grp->list_mutex);
    105	grp->open = NULL;
    106	grp->close = NULL;
    107}
    108
    109
    110/* create a port, port number is returned (-1 on failure);
    111 * the caller needs to unref the port via snd_seq_port_unlock() appropriately
    112 */
    113struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client,
    114						int port)
    115{
    116	struct snd_seq_client_port *new_port, *p;
    117	int num = -1;
    118	
    119	/* sanity check */
    120	if (snd_BUG_ON(!client))
    121		return NULL;
    122
    123	if (client->num_ports >= SNDRV_SEQ_MAX_PORTS) {
    124		pr_warn("ALSA: seq: too many ports for client %d\n", client->number);
    125		return NULL;
    126	}
    127
    128	/* create a new port */
    129	new_port = kzalloc(sizeof(*new_port), GFP_KERNEL);
    130	if (!new_port)
    131		return NULL;	/* failure, out of memory */
    132	/* init port data */
    133	new_port->addr.client = client->number;
    134	new_port->addr.port = -1;
    135	new_port->owner = THIS_MODULE;
    136	sprintf(new_port->name, "port-%d", num);
    137	snd_use_lock_init(&new_port->use_lock);
    138	port_subs_info_init(&new_port->c_src);
    139	port_subs_info_init(&new_port->c_dest);
    140	snd_use_lock_use(&new_port->use_lock);
    141
    142	num = max(port, 0);
    143	mutex_lock(&client->ports_mutex);
    144	write_lock_irq(&client->ports_lock);
    145	list_for_each_entry(p, &client->ports_list_head, list) {
    146		if (p->addr.port > num)
    147			break;
    148		if (port < 0) /* auto-probe mode */
    149			num = p->addr.port + 1;
    150	}
    151	/* insert the new port */
    152	list_add_tail(&new_port->list, &p->list);
    153	client->num_ports++;
    154	new_port->addr.port = num;	/* store the port number in the port */
    155	sprintf(new_port->name, "port-%d", num);
    156	write_unlock_irq(&client->ports_lock);
    157	mutex_unlock(&client->ports_mutex);
    158
    159	return new_port;
    160}
    161
    162/* */
    163static int subscribe_port(struct snd_seq_client *client,
    164			  struct snd_seq_client_port *port,
    165			  struct snd_seq_port_subs_info *grp,
    166			  struct snd_seq_port_subscribe *info, int send_ack);
    167static int unsubscribe_port(struct snd_seq_client *client,
    168			    struct snd_seq_client_port *port,
    169			    struct snd_seq_port_subs_info *grp,
    170			    struct snd_seq_port_subscribe *info, int send_ack);
    171
    172
    173static struct snd_seq_client_port *get_client_port(struct snd_seq_addr *addr,
    174						   struct snd_seq_client **cp)
    175{
    176	struct snd_seq_client_port *p;
    177	*cp = snd_seq_client_use_ptr(addr->client);
    178	if (*cp) {
    179		p = snd_seq_port_use_ptr(*cp, addr->port);
    180		if (! p) {
    181			snd_seq_client_unlock(*cp);
    182			*cp = NULL;
    183		}
    184		return p;
    185	}
    186	return NULL;
    187}
    188
    189static void delete_and_unsubscribe_port(struct snd_seq_client *client,
    190					struct snd_seq_client_port *port,
    191					struct snd_seq_subscribers *subs,
    192					bool is_src, bool ack);
    193
    194static inline struct snd_seq_subscribers *
    195get_subscriber(struct list_head *p, bool is_src)
    196{
    197	if (is_src)
    198		return list_entry(p, struct snd_seq_subscribers, src_list);
    199	else
    200		return list_entry(p, struct snd_seq_subscribers, dest_list);
    201}
    202
    203/*
    204 * remove all subscribers on the list
    205 * this is called from port_delete, for each src and dest list.
    206 */
    207static void clear_subscriber_list(struct snd_seq_client *client,
    208				  struct snd_seq_client_port *port,
    209				  struct snd_seq_port_subs_info *grp,
    210				  int is_src)
    211{
    212	struct list_head *p, *n;
    213
    214	list_for_each_safe(p, n, &grp->list_head) {
    215		struct snd_seq_subscribers *subs;
    216		struct snd_seq_client *c;
    217		struct snd_seq_client_port *aport;
    218
    219		subs = get_subscriber(p, is_src);
    220		if (is_src)
    221			aport = get_client_port(&subs->info.dest, &c);
    222		else
    223			aport = get_client_port(&subs->info.sender, &c);
    224		delete_and_unsubscribe_port(client, port, subs, is_src, false);
    225
    226		if (!aport) {
    227			/* looks like the connected port is being deleted.
    228			 * we decrease the counter, and when both ports are deleted
    229			 * remove the subscriber info
    230			 */
    231			if (atomic_dec_and_test(&subs->ref_count))
    232				kfree(subs);
    233			continue;
    234		}
    235
    236		/* ok we got the connected port */
    237		delete_and_unsubscribe_port(c, aport, subs, !is_src, true);
    238		kfree(subs);
    239		snd_seq_port_unlock(aport);
    240		snd_seq_client_unlock(c);
    241	}
    242}
    243
    244/* delete port data */
    245static int port_delete(struct snd_seq_client *client,
    246		       struct snd_seq_client_port *port)
    247{
    248	/* set closing flag and wait for all port access are gone */
    249	port->closing = 1;
    250	snd_use_lock_sync(&port->use_lock); 
    251
    252	/* clear subscribers info */
    253	clear_subscriber_list(client, port, &port->c_src, true);
    254	clear_subscriber_list(client, port, &port->c_dest, false);
    255
    256	if (port->private_free)
    257		port->private_free(port->private_data);
    258
    259	snd_BUG_ON(port->c_src.count != 0);
    260	snd_BUG_ON(port->c_dest.count != 0);
    261
    262	kfree(port);
    263	return 0;
    264}
    265
    266
    267/* delete a port with the given port id */
    268int snd_seq_delete_port(struct snd_seq_client *client, int port)
    269{
    270	struct snd_seq_client_port *found = NULL, *p;
    271
    272	mutex_lock(&client->ports_mutex);
    273	write_lock_irq(&client->ports_lock);
    274	list_for_each_entry(p, &client->ports_list_head, list) {
    275		if (p->addr.port == port) {
    276			/* ok found.  delete from the list at first */
    277			list_del(&p->list);
    278			client->num_ports--;
    279			found = p;
    280			break;
    281		}
    282	}
    283	write_unlock_irq(&client->ports_lock);
    284	mutex_unlock(&client->ports_mutex);
    285	if (found)
    286		return port_delete(client, found);
    287	else
    288		return -ENOENT;
    289}
    290
    291/* delete the all ports belonging to the given client */
    292int snd_seq_delete_all_ports(struct snd_seq_client *client)
    293{
    294	struct list_head deleted_list;
    295	struct snd_seq_client_port *port, *tmp;
    296	
    297	/* move the port list to deleted_list, and
    298	 * clear the port list in the client data.
    299	 */
    300	mutex_lock(&client->ports_mutex);
    301	write_lock_irq(&client->ports_lock);
    302	if (! list_empty(&client->ports_list_head)) {
    303		list_add(&deleted_list, &client->ports_list_head);
    304		list_del_init(&client->ports_list_head);
    305	} else {
    306		INIT_LIST_HEAD(&deleted_list);
    307	}
    308	client->num_ports = 0;
    309	write_unlock_irq(&client->ports_lock);
    310
    311	/* remove each port in deleted_list */
    312	list_for_each_entry_safe(port, tmp, &deleted_list, list) {
    313		list_del(&port->list);
    314		snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port);
    315		port_delete(client, port);
    316	}
    317	mutex_unlock(&client->ports_mutex);
    318	return 0;
    319}
    320
    321/* set port info fields */
    322int snd_seq_set_port_info(struct snd_seq_client_port * port,
    323			  struct snd_seq_port_info * info)
    324{
    325	if (snd_BUG_ON(!port || !info))
    326		return -EINVAL;
    327
    328	/* set port name */
    329	if (info->name[0])
    330		strscpy(port->name, info->name, sizeof(port->name));
    331	
    332	/* set capabilities */
    333	port->capability = info->capability;
    334	
    335	/* get port type */
    336	port->type = info->type;
    337
    338	/* information about supported channels/voices */
    339	port->midi_channels = info->midi_channels;
    340	port->midi_voices = info->midi_voices;
    341	port->synth_voices = info->synth_voices;
    342
    343	/* timestamping */
    344	port->timestamping = (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0;
    345	port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0;
    346	port->time_queue = info->time_queue;
    347
    348	return 0;
    349}
    350
    351/* get port info fields */
    352int snd_seq_get_port_info(struct snd_seq_client_port * port,
    353			  struct snd_seq_port_info * info)
    354{
    355	if (snd_BUG_ON(!port || !info))
    356		return -EINVAL;
    357
    358	/* get port name */
    359	strscpy(info->name, port->name, sizeof(info->name));
    360	
    361	/* get capabilities */
    362	info->capability = port->capability;
    363
    364	/* get port type */
    365	info->type = port->type;
    366
    367	/* information about supported channels/voices */
    368	info->midi_channels = port->midi_channels;
    369	info->midi_voices = port->midi_voices;
    370	info->synth_voices = port->synth_voices;
    371
    372	/* get subscriber counts */
    373	info->read_use = port->c_src.count;
    374	info->write_use = port->c_dest.count;
    375	
    376	/* timestamping */
    377	info->flags = 0;
    378	if (port->timestamping) {
    379		info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP;
    380		if (port->time_real)
    381			info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL;
    382		info->time_queue = port->time_queue;
    383	}
    384
    385	return 0;
    386}
    387
    388
    389
    390/*
    391 * call callback functions (if any):
    392 * the callbacks are invoked only when the first (for connection) or
    393 * the last subscription (for disconnection) is done.  Second or later
    394 * subscription results in increment of counter, but no callback is
    395 * invoked.
    396 * This feature is useful if these callbacks are associated with
    397 * initialization or termination of devices (see seq_midi.c).
    398 */
    399
    400static int subscribe_port(struct snd_seq_client *client,
    401			  struct snd_seq_client_port *port,
    402			  struct snd_seq_port_subs_info *grp,
    403			  struct snd_seq_port_subscribe *info,
    404			  int send_ack)
    405{
    406	int err = 0;
    407
    408	if (!try_module_get(port->owner))
    409		return -EFAULT;
    410	grp->count++;
    411	if (grp->open && grp->count == 1) {
    412		err = grp->open(port->private_data, info);
    413		if (err < 0) {
    414			module_put(port->owner);
    415			grp->count--;
    416		}
    417	}
    418	if (err >= 0 && send_ack && client->type == USER_CLIENT)
    419		snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
    420						   info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED);
    421
    422	return err;
    423}
    424
    425static int unsubscribe_port(struct snd_seq_client *client,
    426			    struct snd_seq_client_port *port,
    427			    struct snd_seq_port_subs_info *grp,
    428			    struct snd_seq_port_subscribe *info,
    429			    int send_ack)
    430{
    431	int err = 0;
    432
    433	if (! grp->count)
    434		return -EINVAL;
    435	grp->count--;
    436	if (grp->close && grp->count == 0)
    437		err = grp->close(port->private_data, info);
    438	if (send_ack && client->type == USER_CLIENT)
    439		snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
    440						   info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED);
    441	module_put(port->owner);
    442	return err;
    443}
    444
    445
    446
    447/* check if both addresses are identical */
    448static inline int addr_match(struct snd_seq_addr *r, struct snd_seq_addr *s)
    449{
    450	return (r->client == s->client) && (r->port == s->port);
    451}
    452
    453/* check the two subscribe info match */
    454/* if flags is zero, checks only sender and destination addresses */
    455static int match_subs_info(struct snd_seq_port_subscribe *r,
    456			   struct snd_seq_port_subscribe *s)
    457{
    458	if (addr_match(&r->sender, &s->sender) &&
    459	    addr_match(&r->dest, &s->dest)) {
    460		if (r->flags && r->flags == s->flags)
    461			return r->queue == s->queue;
    462		else if (! r->flags)
    463			return 1;
    464	}
    465	return 0;
    466}
    467
    468static int check_and_subscribe_port(struct snd_seq_client *client,
    469				    struct snd_seq_client_port *port,
    470				    struct snd_seq_subscribers *subs,
    471				    bool is_src, bool exclusive, bool ack)
    472{
    473	struct snd_seq_port_subs_info *grp;
    474	struct list_head *p;
    475	struct snd_seq_subscribers *s;
    476	int err;
    477
    478	grp = is_src ? &port->c_src : &port->c_dest;
    479	err = -EBUSY;
    480	down_write(&grp->list_mutex);
    481	if (exclusive) {
    482		if (!list_empty(&grp->list_head))
    483			goto __error;
    484	} else {
    485		if (grp->exclusive)
    486			goto __error;
    487		/* check whether already exists */
    488		list_for_each(p, &grp->list_head) {
    489			s = get_subscriber(p, is_src);
    490			if (match_subs_info(&subs->info, &s->info))
    491				goto __error;
    492		}
    493	}
    494
    495	err = subscribe_port(client, port, grp, &subs->info, ack);
    496	if (err < 0) {
    497		grp->exclusive = 0;
    498		goto __error;
    499	}
    500
    501	/* add to list */
    502	write_lock_irq(&grp->list_lock);
    503	if (is_src)
    504		list_add_tail(&subs->src_list, &grp->list_head);
    505	else
    506		list_add_tail(&subs->dest_list, &grp->list_head);
    507	grp->exclusive = exclusive;
    508	atomic_inc(&subs->ref_count);
    509	write_unlock_irq(&grp->list_lock);
    510	err = 0;
    511
    512 __error:
    513	up_write(&grp->list_mutex);
    514	return err;
    515}
    516
    517/* called with grp->list_mutex held */
    518static void __delete_and_unsubscribe_port(struct snd_seq_client *client,
    519					  struct snd_seq_client_port *port,
    520					  struct snd_seq_subscribers *subs,
    521					  bool is_src, bool ack)
    522{
    523	struct snd_seq_port_subs_info *grp;
    524	struct list_head *list;
    525	bool empty;
    526
    527	grp = is_src ? &port->c_src : &port->c_dest;
    528	list = is_src ? &subs->src_list : &subs->dest_list;
    529	write_lock_irq(&grp->list_lock);
    530	empty = list_empty(list);
    531	if (!empty)
    532		list_del_init(list);
    533	grp->exclusive = 0;
    534	write_unlock_irq(&grp->list_lock);
    535
    536	if (!empty)
    537		unsubscribe_port(client, port, grp, &subs->info, ack);
    538}
    539
    540static void delete_and_unsubscribe_port(struct snd_seq_client *client,
    541					struct snd_seq_client_port *port,
    542					struct snd_seq_subscribers *subs,
    543					bool is_src, bool ack)
    544{
    545	struct snd_seq_port_subs_info *grp;
    546
    547	grp = is_src ? &port->c_src : &port->c_dest;
    548	down_write(&grp->list_mutex);
    549	__delete_and_unsubscribe_port(client, port, subs, is_src, ack);
    550	up_write(&grp->list_mutex);
    551}
    552
    553/* connect two ports */
    554int snd_seq_port_connect(struct snd_seq_client *connector,
    555			 struct snd_seq_client *src_client,
    556			 struct snd_seq_client_port *src_port,
    557			 struct snd_seq_client *dest_client,
    558			 struct snd_seq_client_port *dest_port,
    559			 struct snd_seq_port_subscribe *info)
    560{
    561	struct snd_seq_subscribers *subs;
    562	bool exclusive;
    563	int err;
    564
    565	subs = kzalloc(sizeof(*subs), GFP_KERNEL);
    566	if (!subs)
    567		return -ENOMEM;
    568
    569	subs->info = *info;
    570	atomic_set(&subs->ref_count, 0);
    571	INIT_LIST_HEAD(&subs->src_list);
    572	INIT_LIST_HEAD(&subs->dest_list);
    573
    574	exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE);
    575
    576	err = check_and_subscribe_port(src_client, src_port, subs, true,
    577				       exclusive,
    578				       connector->number != src_client->number);
    579	if (err < 0)
    580		goto error;
    581	err = check_and_subscribe_port(dest_client, dest_port, subs, false,
    582				       exclusive,
    583				       connector->number != dest_client->number);
    584	if (err < 0)
    585		goto error_dest;
    586
    587	return 0;
    588
    589 error_dest:
    590	delete_and_unsubscribe_port(src_client, src_port, subs, true,
    591				    connector->number != src_client->number);
    592 error:
    593	kfree(subs);
    594	return err;
    595}
    596
    597/* remove the connection */
    598int snd_seq_port_disconnect(struct snd_seq_client *connector,
    599			    struct snd_seq_client *src_client,
    600			    struct snd_seq_client_port *src_port,
    601			    struct snd_seq_client *dest_client,
    602			    struct snd_seq_client_port *dest_port,
    603			    struct snd_seq_port_subscribe *info)
    604{
    605	struct snd_seq_port_subs_info *dest = &dest_port->c_dest;
    606	struct snd_seq_subscribers *subs;
    607	int err = -ENOENT;
    608
    609	/* always start from deleting the dest port for avoiding concurrent
    610	 * deletions
    611	 */
    612	down_write(&dest->list_mutex);
    613	/* look for the connection */
    614	list_for_each_entry(subs, &dest->list_head, dest_list) {
    615		if (match_subs_info(info, &subs->info)) {
    616			__delete_and_unsubscribe_port(dest_client, dest_port,
    617						      subs, false,
    618						      connector->number != dest_client->number);
    619			err = 0;
    620			break;
    621		}
    622	}
    623	up_write(&dest->list_mutex);
    624	if (err < 0)
    625		return err;
    626
    627	delete_and_unsubscribe_port(src_client, src_port, subs, true,
    628				    connector->number != src_client->number);
    629	kfree(subs);
    630	return 0;
    631}
    632
    633
    634/* get matched subscriber */
    635int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp,
    636				  struct snd_seq_addr *dest_addr,
    637				  struct snd_seq_port_subscribe *subs)
    638{
    639	struct snd_seq_subscribers *s;
    640	int err = -ENOENT;
    641
    642	down_read(&src_grp->list_mutex);
    643	list_for_each_entry(s, &src_grp->list_head, src_list) {
    644		if (addr_match(dest_addr, &s->info.dest)) {
    645			*subs = s->info;
    646			err = 0;
    647			break;
    648		}
    649	}
    650	up_read(&src_grp->list_mutex);
    651	return err;
    652}
    653
    654/*
    655 * Attach a device driver that wants to receive events from the
    656 * sequencer.  Returns the new port number on success.
    657 * A driver that wants to receive the events converted to midi, will
    658 * use snd_seq_midisynth_register_port().
    659 */
    660/* exported */
    661int snd_seq_event_port_attach(int client,
    662			      struct snd_seq_port_callback *pcbp,
    663			      int cap, int type, int midi_channels,
    664			      int midi_voices, char *portname)
    665{
    666	struct snd_seq_port_info portinfo;
    667	int  ret;
    668
    669	/* Set up the port */
    670	memset(&portinfo, 0, sizeof(portinfo));
    671	portinfo.addr.client = client;
    672	strscpy(portinfo.name, portname ? portname : "Unnamed port",
    673		sizeof(portinfo.name));
    674
    675	portinfo.capability = cap;
    676	portinfo.type = type;
    677	portinfo.kernel = pcbp;
    678	portinfo.midi_channels = midi_channels;
    679	portinfo.midi_voices = midi_voices;
    680
    681	/* Create it */
    682	ret = snd_seq_kernel_client_ctl(client,
    683					SNDRV_SEQ_IOCTL_CREATE_PORT,
    684					&portinfo);
    685
    686	if (ret >= 0)
    687		ret = portinfo.addr.port;
    688
    689	return ret;
    690}
    691EXPORT_SYMBOL(snd_seq_event_port_attach);
    692
    693/*
    694 * Detach the driver from a port.
    695 */
    696/* exported */
    697int snd_seq_event_port_detach(int client, int port)
    698{
    699	struct snd_seq_port_info portinfo;
    700	int  err;
    701
    702	memset(&portinfo, 0, sizeof(portinfo));
    703	portinfo.addr.client = client;
    704	portinfo.addr.port   = port;
    705	err = snd_seq_kernel_client_ctl(client,
    706					SNDRV_SEQ_IOCTL_DELETE_PORT,
    707					&portinfo);
    708
    709	return err;
    710}
    711EXPORT_SYMBOL(snd_seq_event_port_detach);