Root/
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 "seq_system.h" |
26 | #include "seq_ports.h" |
27 | #include "seq_clientmgr.h" |
28 | |
29 | /* |
30 | |
31 | registration of client ports |
32 | |
33 | */ |
34 | |
35 | |
36 | /* |
37 | |
38 | NOTE: the current implementation of the port structure as a linked list is |
39 | not optimal for clients that have many ports. For sending messages to all |
40 | subscribers of a port we first need to find the address of the port |
41 | structure, which means we have to traverse the list. A direct access table |
42 | (array) would be better, but big preallocated arrays waste memory. |
43 | |
44 | Possible actions: |
45 | |
46 | 1) leave it this way, a client does normaly does not have more than a few |
47 | ports |
48 | |
49 | 2) replace the linked list of ports by a array of pointers which is |
50 | dynamicly kmalloced. When a port is added or deleted we can simply allocate |
51 | a new array, copy the corresponding pointers, and delete the old one. We |
52 | then only need a pointer to this array, and an integer that tells us how |
53 | much elements are in array. |
54 | |
55 | */ |
56 | |
57 | /* return pointer to port structure - port is locked if found */ |
58 | struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, |
59 | int num) |
60 | { |
61 | struct snd_seq_client_port *port; |
62 | |
63 | if (client == NULL) |
64 | return NULL; |
65 | read_lock(&client->ports_lock); |
66 | list_for_each_entry(port, &client->ports_list_head, list) { |
67 | if (port->addr.port == num) { |
68 | if (port->closing) |
69 | break; /* deleting now */ |
70 | snd_use_lock_use(&port->use_lock); |
71 | read_unlock(&client->ports_lock); |
72 | return port; |
73 | } |
74 | } |
75 | read_unlock(&client->ports_lock); |
76 | return NULL; /* not found */ |
77 | } |
78 | |
79 | |
80 | /* search for the next port - port is locked if found */ |
81 | struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client, |
82 | struct snd_seq_port_info *pinfo) |
83 | { |
84 | int num; |
85 | struct snd_seq_client_port *port, *found; |
86 | |
87 | num = pinfo->addr.port; |
88 | found = NULL; |
89 | read_lock(&client->ports_lock); |
90 | list_for_each_entry(port, &client->ports_list_head, list) { |
91 | if (port->addr.port < num) |
92 | continue; |
93 | if (port->addr.port == num) { |
94 | found = port; |
95 | break; |
96 | } |
97 | if (found == NULL || port->addr.port < found->addr.port) |
98 | found = port; |
99 | } |
100 | if (found) { |
101 | if (found->closing) |
102 | found = NULL; |
103 | else |
104 | snd_use_lock_use(&found->use_lock); |
105 | } |
106 | read_unlock(&client->ports_lock); |
107 | return found; |
108 | } |
109 | |
110 | |
111 | /* initialize snd_seq_port_subs_info */ |
112 | static void port_subs_info_init(struct snd_seq_port_subs_info *grp) |
113 | { |
114 | INIT_LIST_HEAD(&grp->list_head); |
115 | grp->count = 0; |
116 | grp->exclusive = 0; |
117 | rwlock_init(&grp->list_lock); |
118 | init_rwsem(&grp->list_mutex); |
119 | grp->open = NULL; |
120 | grp->close = NULL; |
121 | } |
122 | |
123 | |
124 | /* create a port, port number is returned (-1 on failure) */ |
125 | struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, |
126 | int port) |
127 | { |
128 | unsigned long flags; |
129 | struct snd_seq_client_port *new_port, *p; |
130 | int num = -1; |
131 | |
132 | /* sanity check */ |
133 | if (snd_BUG_ON(!client)) |
134 | return NULL; |
135 | |
136 | if (client->num_ports >= SNDRV_SEQ_MAX_PORTS - 1) { |
137 | snd_printk(KERN_WARNING "too many ports for client %d\n", client->number); |
138 | return NULL; |
139 | } |
140 | |
141 | /* create a new port */ |
142 | new_port = kzalloc(sizeof(*new_port), GFP_KERNEL); |
143 | if (! new_port) { |
144 | snd_printd("malloc failed for registering client port\n"); |
145 | return NULL; /* failure, out of memory */ |
146 | } |
147 | /* init port data */ |
148 | new_port->addr.client = client->number; |
149 | new_port->addr.port = -1; |
150 | new_port->owner = THIS_MODULE; |
151 | sprintf(new_port->name, "port-%d", num); |
152 | snd_use_lock_init(&new_port->use_lock); |
153 | port_subs_info_init(&new_port->c_src); |
154 | port_subs_info_init(&new_port->c_dest); |
155 | |
156 | num = port >= 0 ? port : 0; |
157 | mutex_lock(&client->ports_mutex); |
158 | write_lock_irqsave(&client->ports_lock, flags); |
159 | list_for_each_entry(p, &client->ports_list_head, list) { |
160 | if (p->addr.port > num) |
161 | break; |
162 | if (port < 0) /* auto-probe mode */ |
163 | num = p->addr.port + 1; |
164 | } |
165 | /* insert the new port */ |
166 | list_add_tail(&new_port->list, &p->list); |
167 | client->num_ports++; |
168 | new_port->addr.port = num; /* store the port number in the port */ |
169 | write_unlock_irqrestore(&client->ports_lock, flags); |
170 | mutex_unlock(&client->ports_mutex); |
171 | sprintf(new_port->name, "port-%d", num); |
172 | |
173 | return new_port; |
174 | } |
175 | |
176 | /* */ |
177 | enum group_type { |
178 | SRC_LIST, DEST_LIST |
179 | }; |
180 | |
181 | static int subscribe_port(struct snd_seq_client *client, |
182 | struct snd_seq_client_port *port, |
183 | struct snd_seq_port_subs_info *grp, |
184 | struct snd_seq_port_subscribe *info, int send_ack); |
185 | static int unsubscribe_port(struct snd_seq_client *client, |
186 | struct snd_seq_client_port *port, |
187 | struct snd_seq_port_subs_info *grp, |
188 | struct snd_seq_port_subscribe *info, int send_ack); |
189 | |
190 | |
191 | static struct snd_seq_client_port *get_client_port(struct snd_seq_addr *addr, |
192 | struct snd_seq_client **cp) |
193 | { |
194 | struct snd_seq_client_port *p; |
195 | *cp = snd_seq_client_use_ptr(addr->client); |
196 | if (*cp) { |
197 | p = snd_seq_port_use_ptr(*cp, addr->port); |
198 | if (! p) { |
199 | snd_seq_client_unlock(*cp); |
200 | *cp = NULL; |
201 | } |
202 | return p; |
203 | } |
204 | return NULL; |
205 | } |
206 | |
207 | /* |
208 | * remove all subscribers on the list |
209 | * this is called from port_delete, for each src and dest list. |
210 | */ |
211 | static void clear_subscriber_list(struct snd_seq_client *client, |
212 | struct snd_seq_client_port *port, |
213 | struct snd_seq_port_subs_info *grp, |
214 | int grptype) |
215 | { |
216 | struct list_head *p, *n; |
217 | |
218 | list_for_each_safe(p, n, &grp->list_head) { |
219 | struct snd_seq_subscribers *subs; |
220 | struct snd_seq_client *c; |
221 | struct snd_seq_client_port *aport; |
222 | |
223 | if (grptype == SRC_LIST) { |
224 | subs = list_entry(p, struct snd_seq_subscribers, src_list); |
225 | aport = get_client_port(&subs->info.dest, &c); |
226 | } else { |
227 | subs = list_entry(p, struct snd_seq_subscribers, dest_list); |
228 | aport = get_client_port(&subs->info.sender, &c); |
229 | } |
230 | list_del(p); |
231 | unsubscribe_port(client, port, grp, &subs->info, 0); |
232 | if (!aport) { |
233 | /* looks like the connected port is being deleted. |
234 | * we decrease the counter, and when both ports are deleted |
235 | * remove the subscriber info |
236 | */ |
237 | if (atomic_dec_and_test(&subs->ref_count)) |
238 | kfree(subs); |
239 | } else { |
240 | /* ok we got the connected port */ |
241 | struct snd_seq_port_subs_info *agrp; |
242 | agrp = (grptype == SRC_LIST) ? &aport->c_dest : &aport->c_src; |
243 | down_write(&agrp->list_mutex); |
244 | if (grptype == SRC_LIST) |
245 | list_del(&subs->dest_list); |
246 | else |
247 | list_del(&subs->src_list); |
248 | up_write(&agrp->list_mutex); |
249 | unsubscribe_port(c, aport, agrp, &subs->info, 1); |
250 | kfree(subs); |
251 | snd_seq_port_unlock(aport); |
252 | snd_seq_client_unlock(c); |
253 | } |
254 | } |
255 | } |
256 | |
257 | /* delete port data */ |
258 | static 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, SRC_LIST); |
267 | clear_subscriber_list(client, port, &port->c_dest, DEST_LIST); |
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 */ |
281 | int 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 */ |
306 | int 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 */ |
337 | int 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 */ |
367 | int 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 | * If callback_all option is set, the callback function is invoked |
415 | * at each connection/disconnection. |
416 | */ |
417 | |
418 | static int subscribe_port(struct snd_seq_client *client, |
419 | struct snd_seq_client_port *port, |
420 | struct snd_seq_port_subs_info *grp, |
421 | struct snd_seq_port_subscribe *info, |
422 | int send_ack) |
423 | { |
424 | int err = 0; |
425 | |
426 | if (!try_module_get(port->owner)) |
427 | return -EFAULT; |
428 | grp->count++; |
429 | if (grp->open && (port->callback_all || grp->count == 1)) { |
430 | err = grp->open(port->private_data, info); |
431 | if (err < 0) { |
432 | module_put(port->owner); |
433 | grp->count--; |
434 | } |
435 | } |
436 | if (err >= 0 && send_ack && client->type == USER_CLIENT) |
437 | snd_seq_client_notify_subscription(port->addr.client, port->addr.port, |
438 | info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED); |
439 | |
440 | return err; |
441 | } |
442 | |
443 | static int unsubscribe_port(struct snd_seq_client *client, |
444 | struct snd_seq_client_port *port, |
445 | struct snd_seq_port_subs_info *grp, |
446 | struct snd_seq_port_subscribe *info, |
447 | int send_ack) |
448 | { |
449 | int err = 0; |
450 | |
451 | if (! grp->count) |
452 | return -EINVAL; |
453 | grp->count--; |
454 | if (grp->close && (port->callback_all || grp->count == 0)) |
455 | err = grp->close(port->private_data, info); |
456 | if (send_ack && client->type == USER_CLIENT) |
457 | snd_seq_client_notify_subscription(port->addr.client, port->addr.port, |
458 | info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED); |
459 | module_put(port->owner); |
460 | return err; |
461 | } |
462 | |
463 | |
464 | |
465 | /* check if both addresses are identical */ |
466 | static inline int addr_match(struct snd_seq_addr *r, struct snd_seq_addr *s) |
467 | { |
468 | return (r->client == s->client) && (r->port == s->port); |
469 | } |
470 | |
471 | /* check the two subscribe info match */ |
472 | /* if flags is zero, checks only sender and destination addresses */ |
473 | static int match_subs_info(struct snd_seq_port_subscribe *r, |
474 | struct snd_seq_port_subscribe *s) |
475 | { |
476 | if (addr_match(&r->sender, &s->sender) && |
477 | addr_match(&r->dest, &s->dest)) { |
478 | if (r->flags && r->flags == s->flags) |
479 | return r->queue == s->queue; |
480 | else if (! r->flags) |
481 | return 1; |
482 | } |
483 | return 0; |
484 | } |
485 | |
486 | |
487 | /* connect two ports */ |
488 | int snd_seq_port_connect(struct snd_seq_client *connector, |
489 | struct snd_seq_client *src_client, |
490 | struct snd_seq_client_port *src_port, |
491 | struct snd_seq_client *dest_client, |
492 | struct snd_seq_client_port *dest_port, |
493 | struct snd_seq_port_subscribe *info) |
494 | { |
495 | struct snd_seq_port_subs_info *src = &src_port->c_src; |
496 | struct snd_seq_port_subs_info *dest = &dest_port->c_dest; |
497 | struct snd_seq_subscribers *subs, *s; |
498 | int err, src_called = 0; |
499 | unsigned long flags; |
500 | int exclusive; |
501 | |
502 | subs = kzalloc(sizeof(*subs), GFP_KERNEL); |
503 | if (! subs) |
504 | return -ENOMEM; |
505 | |
506 | subs->info = *info; |
507 | atomic_set(&subs->ref_count, 2); |
508 | |
509 | down_write(&src->list_mutex); |
510 | down_write_nested(&dest->list_mutex, SINGLE_DEPTH_NESTING); |
511 | |
512 | exclusive = info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE ? 1 : 0; |
513 | err = -EBUSY; |
514 | if (exclusive) { |
515 | if (! list_empty(&src->list_head) || ! list_empty(&dest->list_head)) |
516 | goto __error; |
517 | } else { |
518 | if (src->exclusive || dest->exclusive) |
519 | goto __error; |
520 | /* check whether already exists */ |
521 | list_for_each_entry(s, &src->list_head, src_list) { |
522 | if (match_subs_info(info, &s->info)) |
523 | goto __error; |
524 | } |
525 | list_for_each_entry(s, &dest->list_head, dest_list) { |
526 | if (match_subs_info(info, &s->info)) |
527 | goto __error; |
528 | } |
529 | } |
530 | |
531 | if ((err = subscribe_port(src_client, src_port, src, info, |
532 | connector->number != src_client->number)) < 0) |
533 | goto __error; |
534 | src_called = 1; |
535 | |
536 | if ((err = subscribe_port(dest_client, dest_port, dest, info, |
537 | connector->number != dest_client->number)) < 0) |
538 | goto __error; |
539 | |
540 | /* add to list */ |
541 | write_lock_irqsave(&src->list_lock, flags); |
542 | // write_lock(&dest->list_lock); // no other lock yet |
543 | list_add_tail(&subs->src_list, &src->list_head); |
544 | list_add_tail(&subs->dest_list, &dest->list_head); |
545 | // write_unlock(&dest->list_lock); // no other lock yet |
546 | write_unlock_irqrestore(&src->list_lock, flags); |
547 | |
548 | src->exclusive = dest->exclusive = exclusive; |
549 | |
550 | up_write(&dest->list_mutex); |
551 | up_write(&src->list_mutex); |
552 | return 0; |
553 | |
554 | __error: |
555 | if (src_called) |
556 | unsubscribe_port(src_client, src_port, src, info, |
557 | connector->number != src_client->number); |
558 | kfree(subs); |
559 | up_write(&dest->list_mutex); |
560 | up_write(&src->list_mutex); |
561 | return err; |
562 | } |
563 | |
564 | |
565 | /* remove the connection */ |
566 | int snd_seq_port_disconnect(struct snd_seq_client *connector, |
567 | struct snd_seq_client *src_client, |
568 | struct snd_seq_client_port *src_port, |
569 | struct snd_seq_client *dest_client, |
570 | struct snd_seq_client_port *dest_port, |
571 | struct snd_seq_port_subscribe *info) |
572 | { |
573 | struct snd_seq_port_subs_info *src = &src_port->c_src; |
574 | struct snd_seq_port_subs_info *dest = &dest_port->c_dest; |
575 | struct snd_seq_subscribers *subs; |
576 | int err = -ENOENT; |
577 | unsigned long flags; |
578 | |
579 | down_write(&src->list_mutex); |
580 | down_write_nested(&dest->list_mutex, SINGLE_DEPTH_NESTING); |
581 | |
582 | /* look for the connection */ |
583 | list_for_each_entry(subs, &src->list_head, src_list) { |
584 | if (match_subs_info(info, &subs->info)) { |
585 | write_lock_irqsave(&src->list_lock, flags); |
586 | // write_lock(&dest->list_lock); // no lock yet |
587 | list_del(&subs->src_list); |
588 | list_del(&subs->dest_list); |
589 | // write_unlock(&dest->list_lock); |
590 | write_unlock_irqrestore(&src->list_lock, flags); |
591 | src->exclusive = dest->exclusive = 0; |
592 | unsubscribe_port(src_client, src_port, src, info, |
593 | connector->number != src_client->number); |
594 | unsubscribe_port(dest_client, dest_port, dest, info, |
595 | connector->number != dest_client->number); |
596 | kfree(subs); |
597 | err = 0; |
598 | break; |
599 | } |
600 | } |
601 | |
602 | up_write(&dest->list_mutex); |
603 | up_write(&src->list_mutex); |
604 | return err; |
605 | } |
606 | |
607 | |
608 | /* get matched subscriber */ |
609 | struct snd_seq_subscribers *snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, |
610 | struct snd_seq_addr *dest_addr) |
611 | { |
612 | struct snd_seq_subscribers *s, *found = NULL; |
613 | |
614 | down_read(&src_grp->list_mutex); |
615 | list_for_each_entry(s, &src_grp->list_head, src_list) { |
616 | if (addr_match(dest_addr, &s->info.dest)) { |
617 | found = s; |
618 | break; |
619 | } |
620 | } |
621 | up_read(&src_grp->list_mutex); |
622 | return found; |
623 | } |
624 | |
625 | /* |
626 | * Attach a device driver that wants to receive events from the |
627 | * sequencer. Returns the new port number on success. |
628 | * A driver that wants to receive the events converted to midi, will |
629 | * use snd_seq_midisynth_register_port(). |
630 | */ |
631 | /* exported */ |
632 | int snd_seq_event_port_attach(int client, |
633 | struct snd_seq_port_callback *pcbp, |
634 | int cap, int type, int midi_channels, |
635 | int midi_voices, char *portname) |
636 | { |
637 | struct snd_seq_port_info portinfo; |
638 | int ret; |
639 | |
640 | /* Set up the port */ |
641 | memset(&portinfo, 0, sizeof(portinfo)); |
642 | portinfo.addr.client = client; |
643 | strlcpy(portinfo.name, portname ? portname : "Unamed port", |
644 | sizeof(portinfo.name)); |
645 | |
646 | portinfo.capability = cap; |
647 | portinfo.type = type; |
648 | portinfo.kernel = pcbp; |
649 | portinfo.midi_channels = midi_channels; |
650 | portinfo.midi_voices = midi_voices; |
651 | |
652 | /* Create it */ |
653 | ret = snd_seq_kernel_client_ctl(client, |
654 | SNDRV_SEQ_IOCTL_CREATE_PORT, |
655 | &portinfo); |
656 | |
657 | if (ret >= 0) |
658 | ret = portinfo.addr.port; |
659 | |
660 | return ret; |
661 | } |
662 | |
663 | EXPORT_SYMBOL(snd_seq_event_port_attach); |
664 | |
665 | /* |
666 | * Detach the driver from a port. |
667 | */ |
668 | /* exported */ |
669 | int snd_seq_event_port_detach(int client, int port) |
670 | { |
671 | struct snd_seq_port_info portinfo; |
672 | int err; |
673 | |
674 | memset(&portinfo, 0, sizeof(portinfo)); |
675 | portinfo.addr.client = client; |
676 | portinfo.addr.port = port; |
677 | err = snd_seq_kernel_client_ctl(client, |
678 | SNDRV_SEQ_IOCTL_DELETE_PORT, |
679 | &portinfo); |
680 | |
681 | return err; |
682 | } |
683 | |
684 | EXPORT_SYMBOL(snd_seq_event_port_detach); |
685 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9