Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * ALSA sequencer device management | |
3 | * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation; either version 2 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program; if not, write to the Free Software | |
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
18 | * | |
19 | * | |
20 | *---------------------------------------------------------------- | |
21 | * | |
22 | * This device handler separates the card driver module from sequencer | |
23 | * stuff (sequencer core, synth drivers, etc), so that user can avoid | |
24 | * to spend unnecessary resources e.g. if he needs only listening to | |
25 | * MP3s. | |
26 | * | |
27 | * The card (or lowlevel) driver creates a sequencer device entry | |
28 | * via snd_seq_device_new(). This is an entry pointer to communicate | |
29 | * with the sequencer device "driver", which is involved with the | |
30 | * actual part to communicate with the sequencer core. | |
31 | * Each sequencer device entry has an id string and the corresponding | |
32 | * driver with the same id is loaded when required. For example, | |
33 | * lowlevel codes to access emu8000 chip on sbawe card are included in | |
34 | * emu8000-synth module. To activate this module, the hardware | |
35 | * resources like i/o port are passed via snd_seq_device argument. | |
36 | * | |
37 | */ | |
38 | ||
39 | #include <sound/driver.h> | |
40 | #include <linux/init.h> | |
41 | #include <sound/core.h> | |
42 | #include <sound/info.h> | |
43 | #include <sound/seq_device.h> | |
44 | #include <sound/seq_kernel.h> | |
45 | #include <sound/initval.h> | |
46 | #include <linux/kmod.h> | |
47 | #include <linux/slab.h> | |
48 | ||
49 | MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); | |
50 | MODULE_DESCRIPTION("ALSA sequencer device management"); | |
51 | MODULE_LICENSE("GPL"); | |
52 | ||
1da177e4 LT |
53 | /* driver state */ |
54 | #define DRIVER_EMPTY 0 | |
55 | #define DRIVER_LOADED (1<<0) | |
56 | #define DRIVER_REQUESTED (1<<1) | |
57 | #define DRIVER_LOCKED (1<<2) | |
58 | ||
59 | struct ops_list { | |
60 | char id[ID_LEN]; /* driver id */ | |
61 | int driver; /* driver state */ | |
62 | int used; /* reference counter */ | |
63 | int argsize; /* argument size */ | |
64 | ||
65 | /* operators */ | |
c7e0b5bf | 66 | struct snd_seq_dev_ops ops; |
1da177e4 LT |
67 | |
68 | /* registred devices */ | |
69 | struct list_head dev_list; /* list of devices */ | |
70 | int num_devices; /* number of associated devices */ | |
71 | int num_init_devices; /* number of initialized devices */ | |
72 | struct semaphore reg_mutex; | |
73 | ||
74 | struct list_head list; /* next driver */ | |
75 | }; | |
76 | ||
77 | ||
78 | static LIST_HEAD(opslist); | |
79 | static int num_ops; | |
80 | static DECLARE_MUTEX(ops_mutex); | |
04f141a8 | 81 | #ifdef CONFIG_PROC_FS |
c7e0b5bf | 82 | static struct snd_info_entry *info_entry = NULL; |
04f141a8 | 83 | #endif |
1da177e4 LT |
84 | |
85 | /* | |
86 | * prototypes | |
87 | */ | |
c7e0b5bf TI |
88 | static int snd_seq_device_free(struct snd_seq_device *dev); |
89 | static int snd_seq_device_dev_free(struct snd_device *device); | |
90 | static int snd_seq_device_dev_register(struct snd_device *device); | |
91 | static int snd_seq_device_dev_disconnect(struct snd_device *device); | |
92 | static int snd_seq_device_dev_unregister(struct snd_device *device); | |
93 | ||
94 | static int init_device(struct snd_seq_device *dev, struct ops_list *ops); | |
95 | static int free_device(struct snd_seq_device *dev, struct ops_list *ops); | |
96 | static struct ops_list *find_driver(char *id, int create_if_empty); | |
97 | static struct ops_list *create_driver(char *id); | |
98 | static void unlock_driver(struct ops_list *ops); | |
1da177e4 LT |
99 | static void remove_drivers(void); |
100 | ||
101 | /* | |
102 | * show all drivers and their status | |
103 | */ | |
104 | ||
04f141a8 | 105 | #ifdef CONFIG_PROC_FS |
c7e0b5bf TI |
106 | static void snd_seq_device_info(struct snd_info_entry *entry, |
107 | struct snd_info_buffer *buffer) | |
1da177e4 LT |
108 | { |
109 | struct list_head *head; | |
110 | ||
111 | down(&ops_mutex); | |
112 | list_for_each(head, &opslist) { | |
c7e0b5bf | 113 | struct ops_list *ops = list_entry(head, struct ops_list, list); |
1da177e4 LT |
114 | snd_iprintf(buffer, "snd-%s%s%s%s,%d\n", |
115 | ops->id, | |
116 | ops->driver & DRIVER_LOADED ? ",loaded" : (ops->driver == DRIVER_EMPTY ? ",empty" : ""), | |
117 | ops->driver & DRIVER_REQUESTED ? ",requested" : "", | |
118 | ops->driver & DRIVER_LOCKED ? ",locked" : "", | |
119 | ops->num_devices); | |
120 | } | |
121 | up(&ops_mutex); | |
122 | } | |
04f141a8 | 123 | #endif |
1da177e4 LT |
124 | |
125 | /* | |
126 | * load all registered drivers (called from seq_clientmgr.c) | |
127 | */ | |
128 | ||
129 | #ifdef CONFIG_KMOD | |
130 | /* avoid auto-loading during module_init() */ | |
131 | static int snd_seq_in_init; | |
132 | void snd_seq_autoload_lock(void) | |
133 | { | |
134 | snd_seq_in_init++; | |
135 | } | |
136 | ||
137 | void snd_seq_autoload_unlock(void) | |
138 | { | |
139 | snd_seq_in_init--; | |
140 | } | |
141 | #endif | |
142 | ||
143 | void snd_seq_device_load_drivers(void) | |
144 | { | |
145 | #ifdef CONFIG_KMOD | |
146 | struct list_head *head; | |
147 | ||
148 | /* Calling request_module during module_init() | |
149 | * may cause blocking. | |
150 | */ | |
151 | if (snd_seq_in_init) | |
152 | return; | |
153 | ||
154 | if (! current->fs->root) | |
155 | return; | |
156 | ||
157 | down(&ops_mutex); | |
158 | list_for_each(head, &opslist) { | |
c7e0b5bf | 159 | struct ops_list *ops = list_entry(head, struct ops_list, list); |
1da177e4 LT |
160 | if (! (ops->driver & DRIVER_LOADED) && |
161 | ! (ops->driver & DRIVER_REQUESTED)) { | |
162 | ops->used++; | |
163 | up(&ops_mutex); | |
164 | ops->driver |= DRIVER_REQUESTED; | |
165 | request_module("snd-%s", ops->id); | |
166 | down(&ops_mutex); | |
167 | ops->used--; | |
168 | } | |
169 | } | |
170 | up(&ops_mutex); | |
171 | #endif | |
172 | } | |
173 | ||
174 | /* | |
175 | * register a sequencer device | |
176 | * card = card info (NULL allowed) | |
177 | * device = device number (if any) | |
178 | * id = id of driver | |
179 | * result = return pointer (NULL allowed if unnecessary) | |
180 | */ | |
c7e0b5bf TI |
181 | int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize, |
182 | struct snd_seq_device **result) | |
1da177e4 | 183 | { |
c7e0b5bf TI |
184 | struct snd_seq_device *dev; |
185 | struct ops_list *ops; | |
1da177e4 | 186 | int err; |
c7e0b5bf | 187 | static struct snd_device_ops dops = { |
1da177e4 LT |
188 | .dev_free = snd_seq_device_dev_free, |
189 | .dev_register = snd_seq_device_dev_register, | |
190 | .dev_disconnect = snd_seq_device_dev_disconnect, | |
191 | .dev_unregister = snd_seq_device_dev_unregister | |
192 | }; | |
193 | ||
194 | if (result) | |
195 | *result = NULL; | |
196 | ||
197 | snd_assert(id != NULL, return -EINVAL); | |
198 | ||
199 | ops = find_driver(id, 1); | |
200 | if (ops == NULL) | |
201 | return -ENOMEM; | |
202 | ||
ecca82b4 | 203 | dev = kzalloc(sizeof(*dev)*2 + argsize, GFP_KERNEL); |
1da177e4 LT |
204 | if (dev == NULL) { |
205 | unlock_driver(ops); | |
206 | return -ENOMEM; | |
207 | } | |
208 | ||
209 | /* set up device info */ | |
210 | dev->card = card; | |
211 | dev->device = device; | |
212 | strlcpy(dev->id, id, sizeof(dev->id)); | |
213 | dev->argsize = argsize; | |
214 | dev->status = SNDRV_SEQ_DEVICE_FREE; | |
215 | ||
216 | /* add this device to the list */ | |
217 | down(&ops->reg_mutex); | |
218 | list_add_tail(&dev->list, &ops->dev_list); | |
219 | ops->num_devices++; | |
220 | up(&ops->reg_mutex); | |
221 | ||
222 | unlock_driver(ops); | |
223 | ||
224 | if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) { | |
225 | snd_seq_device_free(dev); | |
226 | return err; | |
227 | } | |
228 | ||
229 | if (result) | |
230 | *result = dev; | |
231 | ||
232 | return 0; | |
233 | } | |
234 | ||
235 | /* | |
236 | * free the existing device | |
237 | */ | |
c7e0b5bf | 238 | static int snd_seq_device_free(struct snd_seq_device *dev) |
1da177e4 | 239 | { |
c7e0b5bf | 240 | struct ops_list *ops; |
1da177e4 LT |
241 | |
242 | snd_assert(dev != NULL, return -EINVAL); | |
243 | ||
244 | ops = find_driver(dev->id, 0); | |
245 | if (ops == NULL) | |
246 | return -ENXIO; | |
247 | ||
248 | /* remove the device from the list */ | |
249 | down(&ops->reg_mutex); | |
250 | list_del(&dev->list); | |
251 | ops->num_devices--; | |
252 | up(&ops->reg_mutex); | |
253 | ||
254 | free_device(dev, ops); | |
255 | if (dev->private_free) | |
256 | dev->private_free(dev); | |
257 | kfree(dev); | |
258 | ||
259 | unlock_driver(ops); | |
260 | ||
261 | return 0; | |
262 | } | |
263 | ||
c7e0b5bf | 264 | static int snd_seq_device_dev_free(struct snd_device *device) |
1da177e4 | 265 | { |
c7e0b5bf | 266 | struct snd_seq_device *dev = device->device_data; |
1da177e4 LT |
267 | return snd_seq_device_free(dev); |
268 | } | |
269 | ||
270 | /* | |
271 | * register the device | |
272 | */ | |
c7e0b5bf | 273 | static int snd_seq_device_dev_register(struct snd_device *device) |
1da177e4 | 274 | { |
c7e0b5bf TI |
275 | struct snd_seq_device *dev = device->device_data; |
276 | struct ops_list *ops; | |
1da177e4 LT |
277 | |
278 | ops = find_driver(dev->id, 0); | |
279 | if (ops == NULL) | |
280 | return -ENOENT; | |
281 | ||
282 | /* initialize this device if the corresponding driver was | |
283 | * already loaded | |
284 | */ | |
285 | if (ops->driver & DRIVER_LOADED) | |
286 | init_device(dev, ops); | |
287 | ||
288 | unlock_driver(ops); | |
289 | return 0; | |
290 | } | |
291 | ||
292 | /* | |
293 | * disconnect the device | |
294 | */ | |
c7e0b5bf | 295 | static int snd_seq_device_dev_disconnect(struct snd_device *device) |
1da177e4 | 296 | { |
c7e0b5bf TI |
297 | struct snd_seq_device *dev = device->device_data; |
298 | struct ops_list *ops; | |
1da177e4 LT |
299 | |
300 | ops = find_driver(dev->id, 0); | |
301 | if (ops == NULL) | |
302 | return -ENOENT; | |
303 | ||
304 | free_device(dev, ops); | |
305 | ||
306 | unlock_driver(ops); | |
307 | return 0; | |
308 | } | |
309 | ||
310 | /* | |
311 | * unregister the existing device | |
312 | */ | |
c7e0b5bf | 313 | static int snd_seq_device_dev_unregister(struct snd_device *device) |
1da177e4 | 314 | { |
c7e0b5bf | 315 | struct snd_seq_device *dev = device->device_data; |
1da177e4 LT |
316 | return snd_seq_device_free(dev); |
317 | } | |
318 | ||
319 | /* | |
320 | * register device driver | |
321 | * id = driver id | |
322 | * entry = driver operators - duplicated to each instance | |
323 | */ | |
c7e0b5bf TI |
324 | int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry, |
325 | int argsize) | |
1da177e4 LT |
326 | { |
327 | struct list_head *head; | |
c7e0b5bf | 328 | struct ops_list *ops; |
1da177e4 LT |
329 | |
330 | if (id == NULL || entry == NULL || | |
331 | entry->init_device == NULL || entry->free_device == NULL) | |
332 | return -EINVAL; | |
333 | ||
334 | snd_seq_autoload_lock(); | |
335 | ops = find_driver(id, 1); | |
336 | if (ops == NULL) { | |
337 | snd_seq_autoload_unlock(); | |
338 | return -ENOMEM; | |
339 | } | |
340 | if (ops->driver & DRIVER_LOADED) { | |
341 | snd_printk(KERN_WARNING "driver_register: driver '%s' already exists\n", id); | |
342 | unlock_driver(ops); | |
343 | snd_seq_autoload_unlock(); | |
344 | return -EBUSY; | |
345 | } | |
346 | ||
347 | down(&ops->reg_mutex); | |
348 | /* copy driver operators */ | |
349 | ops->ops = *entry; | |
350 | ops->driver |= DRIVER_LOADED; | |
351 | ops->argsize = argsize; | |
352 | ||
353 | /* initialize existing devices if necessary */ | |
354 | list_for_each(head, &ops->dev_list) { | |
c7e0b5bf | 355 | struct snd_seq_device *dev = list_entry(head, struct snd_seq_device, list); |
1da177e4 LT |
356 | init_device(dev, ops); |
357 | } | |
358 | up(&ops->reg_mutex); | |
359 | ||
360 | unlock_driver(ops); | |
361 | snd_seq_autoload_unlock(); | |
362 | ||
363 | return 0; | |
364 | } | |
365 | ||
366 | ||
367 | /* | |
368 | * create driver record | |
369 | */ | |
c7e0b5bf | 370 | static struct ops_list * create_driver(char *id) |
1da177e4 | 371 | { |
c7e0b5bf | 372 | struct ops_list *ops; |
1da177e4 LT |
373 | |
374 | ops = kmalloc(sizeof(*ops), GFP_KERNEL); | |
375 | if (ops == NULL) | |
376 | return ops; | |
377 | memset(ops, 0, sizeof(*ops)); | |
378 | ||
379 | /* set up driver entry */ | |
380 | strlcpy(ops->id, id, sizeof(ops->id)); | |
381 | init_MUTEX(&ops->reg_mutex); | |
382 | ops->driver = DRIVER_EMPTY; | |
383 | INIT_LIST_HEAD(&ops->dev_list); | |
384 | /* lock this instance */ | |
385 | ops->used = 1; | |
386 | ||
387 | /* register driver entry */ | |
388 | down(&ops_mutex); | |
389 | list_add_tail(&ops->list, &opslist); | |
390 | num_ops++; | |
391 | up(&ops_mutex); | |
392 | ||
393 | return ops; | |
394 | } | |
395 | ||
396 | ||
397 | /* | |
398 | * unregister the specified driver | |
399 | */ | |
400 | int snd_seq_device_unregister_driver(char *id) | |
401 | { | |
402 | struct list_head *head; | |
c7e0b5bf | 403 | struct ops_list *ops; |
1da177e4 LT |
404 | |
405 | ops = find_driver(id, 0); | |
406 | if (ops == NULL) | |
407 | return -ENXIO; | |
408 | if (! (ops->driver & DRIVER_LOADED) || | |
409 | (ops->driver & DRIVER_LOCKED)) { | |
c7e0b5bf TI |
410 | snd_printk(KERN_ERR "driver_unregister: cannot unload driver '%s': status=%x\n", |
411 | id, ops->driver); | |
1da177e4 LT |
412 | unlock_driver(ops); |
413 | return -EBUSY; | |
414 | } | |
415 | ||
416 | /* close and release all devices associated with this driver */ | |
417 | down(&ops->reg_mutex); | |
418 | ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */ | |
419 | list_for_each(head, &ops->dev_list) { | |
c7e0b5bf | 420 | struct snd_seq_device *dev = list_entry(head, struct snd_seq_device, list); |
1da177e4 LT |
421 | free_device(dev, ops); |
422 | } | |
423 | ||
424 | ops->driver = 0; | |
425 | if (ops->num_init_devices > 0) | |
c7e0b5bf TI |
426 | snd_printk(KERN_ERR "free_driver: init_devices > 0!! (%d)\n", |
427 | ops->num_init_devices); | |
1da177e4 LT |
428 | up(&ops->reg_mutex); |
429 | ||
430 | unlock_driver(ops); | |
431 | ||
432 | /* remove empty driver entries */ | |
433 | remove_drivers(); | |
434 | ||
435 | return 0; | |
436 | } | |
437 | ||
438 | ||
439 | /* | |
440 | * remove empty driver entries | |
441 | */ | |
442 | static void remove_drivers(void) | |
443 | { | |
444 | struct list_head *head; | |
445 | ||
446 | down(&ops_mutex); | |
447 | head = opslist.next; | |
448 | while (head != &opslist) { | |
c7e0b5bf | 449 | struct ops_list *ops = list_entry(head, struct ops_list, list); |
1da177e4 LT |
450 | if (! (ops->driver & DRIVER_LOADED) && |
451 | ops->used == 0 && ops->num_devices == 0) { | |
452 | head = head->next; | |
453 | list_del(&ops->list); | |
454 | kfree(ops); | |
455 | num_ops--; | |
456 | } else | |
457 | head = head->next; | |
458 | } | |
459 | up(&ops_mutex); | |
460 | } | |
461 | ||
462 | /* | |
463 | * initialize the device - call init_device operator | |
464 | */ | |
c7e0b5bf | 465 | static int init_device(struct snd_seq_device *dev, struct ops_list *ops) |
1da177e4 LT |
466 | { |
467 | if (! (ops->driver & DRIVER_LOADED)) | |
468 | return 0; /* driver is not loaded yet */ | |
469 | if (dev->status != SNDRV_SEQ_DEVICE_FREE) | |
470 | return 0; /* already initialized */ | |
471 | if (ops->argsize != dev->argsize) { | |
c7e0b5bf TI |
472 | snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", |
473 | dev->name, ops->id, ops->argsize, dev->argsize); | |
1da177e4 LT |
474 | return -EINVAL; |
475 | } | |
476 | if (ops->ops.init_device(dev) >= 0) { | |
477 | dev->status = SNDRV_SEQ_DEVICE_REGISTERED; | |
478 | ops->num_init_devices++; | |
479 | } else { | |
c7e0b5bf TI |
480 | snd_printk(KERN_ERR "init_device failed: %s: %s\n", |
481 | dev->name, dev->id); | |
1da177e4 LT |
482 | } |
483 | ||
484 | return 0; | |
485 | } | |
486 | ||
487 | /* | |
488 | * release the device - call free_device operator | |
489 | */ | |
c7e0b5bf | 490 | static int free_device(struct snd_seq_device *dev, struct ops_list *ops) |
1da177e4 LT |
491 | { |
492 | int result; | |
493 | ||
494 | if (! (ops->driver & DRIVER_LOADED)) | |
495 | return 0; /* driver is not loaded yet */ | |
496 | if (dev->status != SNDRV_SEQ_DEVICE_REGISTERED) | |
497 | return 0; /* not registered */ | |
498 | if (ops->argsize != dev->argsize) { | |
c7e0b5bf TI |
499 | snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", |
500 | dev->name, ops->id, ops->argsize, dev->argsize); | |
1da177e4 LT |
501 | return -EINVAL; |
502 | } | |
503 | if ((result = ops->ops.free_device(dev)) >= 0 || result == -ENXIO) { | |
504 | dev->status = SNDRV_SEQ_DEVICE_FREE; | |
505 | dev->driver_data = NULL; | |
506 | ops->num_init_devices--; | |
507 | } else { | |
c7e0b5bf TI |
508 | snd_printk(KERN_ERR "free_device failed: %s: %s\n", |
509 | dev->name, dev->id); | |
1da177e4 LT |
510 | } |
511 | ||
512 | return 0; | |
513 | } | |
514 | ||
515 | /* | |
516 | * find the matching driver with given id | |
517 | */ | |
c7e0b5bf | 518 | static struct ops_list * find_driver(char *id, int create_if_empty) |
1da177e4 LT |
519 | { |
520 | struct list_head *head; | |
521 | ||
522 | down(&ops_mutex); | |
523 | list_for_each(head, &opslist) { | |
c7e0b5bf | 524 | struct ops_list *ops = list_entry(head, struct ops_list, list); |
1da177e4 LT |
525 | if (strcmp(ops->id, id) == 0) { |
526 | ops->used++; | |
527 | up(&ops_mutex); | |
528 | return ops; | |
529 | } | |
530 | } | |
531 | up(&ops_mutex); | |
532 | if (create_if_empty) | |
533 | return create_driver(id); | |
534 | return NULL; | |
535 | } | |
536 | ||
c7e0b5bf | 537 | static void unlock_driver(struct ops_list *ops) |
1da177e4 LT |
538 | { |
539 | down(&ops_mutex); | |
540 | ops->used--; | |
541 | up(&ops_mutex); | |
542 | } | |
543 | ||
544 | ||
545 | /* | |
546 | * module part | |
547 | */ | |
548 | ||
549 | static int __init alsa_seq_device_init(void) | |
550 | { | |
04f141a8 | 551 | #ifdef CONFIG_PROC_FS |
c7e0b5bf TI |
552 | info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", |
553 | snd_seq_root); | |
1da177e4 LT |
554 | if (info_entry == NULL) |
555 | return -ENOMEM; | |
556 | info_entry->content = SNDRV_INFO_CONTENT_TEXT; | |
557 | info_entry->c.text.read_size = 2048; | |
558 | info_entry->c.text.read = snd_seq_device_info; | |
559 | if (snd_info_register(info_entry) < 0) { | |
560 | snd_info_free_entry(info_entry); | |
561 | return -ENOMEM; | |
562 | } | |
04f141a8 | 563 | #endif |
1da177e4 LT |
564 | return 0; |
565 | } | |
566 | ||
567 | static void __exit alsa_seq_device_exit(void) | |
568 | { | |
569 | remove_drivers(); | |
04f141a8 | 570 | #ifdef CONFIG_PROC_FS |
1da177e4 | 571 | snd_info_unregister(info_entry); |
04f141a8 | 572 | #endif |
1da177e4 LT |
573 | if (num_ops) |
574 | snd_printk(KERN_ERR "drivers not released (%d)\n", num_ops); | |
575 | } | |
576 | ||
577 | module_init(alsa_seq_device_init) | |
578 | module_exit(alsa_seq_device_exit) | |
579 | ||
580 | EXPORT_SYMBOL(snd_seq_device_load_drivers); | |
581 | EXPORT_SYMBOL(snd_seq_device_new); | |
582 | EXPORT_SYMBOL(snd_seq_device_register_driver); | |
583 | EXPORT_SYMBOL(snd_seq_device_unregister_driver); | |
584 | #ifdef CONFIG_KMOD | |
585 | EXPORT_SYMBOL(snd_seq_autoload_lock); | |
586 | EXPORT_SYMBOL(snd_seq_autoload_unlock); | |
587 | #endif |