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