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