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 | ||
1da177e4 LT |
39 | #include <linux/init.h> |
40 | #include <sound/core.h> | |
41 | #include <sound/info.h> | |
42 | #include <sound/seq_device.h> | |
43 | #include <sound/seq_kernel.h> | |
44 | #include <sound/initval.h> | |
45 | #include <linux/kmod.h> | |
46 | #include <linux/slab.h> | |
1a60d4c5 | 47 | #include <linux/mutex.h> |
1da177e4 LT |
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 */ | |
1a60d4c5 | 72 | struct mutex reg_mutex; |
1da177e4 LT |
73 | |
74 | struct list_head list; /* next driver */ | |
75 | }; | |
76 | ||
77 | ||
78 | static LIST_HEAD(opslist); | |
79 | static int num_ops; | |
1a60d4c5 | 80 | static DEFINE_MUTEX(ops_mutex); |
04f141a8 | 81 | #ifdef CONFIG_PROC_FS |
6581f4e7 | 82 | static struct snd_info_entry *info_entry; |
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); | |
c7e0b5bf TI |
92 | |
93 | static int init_device(struct snd_seq_device *dev, struct ops_list *ops); | |
94 | static int free_device(struct snd_seq_device *dev, struct ops_list *ops); | |
95 | static struct ops_list *find_driver(char *id, int create_if_empty); | |
96 | static struct ops_list *create_driver(char *id); | |
97 | static void unlock_driver(struct ops_list *ops); | |
1da177e4 LT |
98 | static void remove_drivers(void); |
99 | ||
100 | /* | |
101 | * show all drivers and their status | |
102 | */ | |
103 | ||
04f141a8 | 104 | #ifdef CONFIG_PROC_FS |
c7e0b5bf TI |
105 | static void snd_seq_device_info(struct snd_info_entry *entry, |
106 | struct snd_info_buffer *buffer) | |
1da177e4 | 107 | { |
9244b2c3 | 108 | struct ops_list *ops; |
1da177e4 | 109 | |
1a60d4c5 | 110 | mutex_lock(&ops_mutex); |
9244b2c3 | 111 | list_for_each_entry(ops, &opslist, list) { |
1da177e4 LT |
112 | snd_iprintf(buffer, "snd-%s%s%s%s,%d\n", |
113 | ops->id, | |
114 | ops->driver & DRIVER_LOADED ? ",loaded" : (ops->driver == DRIVER_EMPTY ? ",empty" : ""), | |
115 | ops->driver & DRIVER_REQUESTED ? ",requested" : "", | |
116 | ops->driver & DRIVER_LOCKED ? ",locked" : "", | |
117 | ops->num_devices); | |
118 | } | |
1a60d4c5 | 119 | mutex_unlock(&ops_mutex); |
1da177e4 | 120 | } |
04f141a8 | 121 | #endif |
1da177e4 LT |
122 | |
123 | /* | |
124 | * load all registered drivers (called from seq_clientmgr.c) | |
125 | */ | |
126 | ||
ee2da997 | 127 | #ifdef CONFIG_MODULES |
1da177e4 LT |
128 | /* avoid auto-loading during module_init() */ |
129 | static int snd_seq_in_init; | |
130 | void snd_seq_autoload_lock(void) | |
131 | { | |
132 | snd_seq_in_init++; | |
133 | } | |
134 | ||
135 | void snd_seq_autoload_unlock(void) | |
136 | { | |
137 | snd_seq_in_init--; | |
138 | } | |
139 | #endif | |
140 | ||
141 | void snd_seq_device_load_drivers(void) | |
142 | { | |
ee2da997 | 143 | #ifdef CONFIG_MODULES |
9244b2c3 | 144 | struct ops_list *ops; |
1da177e4 LT |
145 | |
146 | /* Calling request_module during module_init() | |
147 | * may cause blocking. | |
148 | */ | |
149 | if (snd_seq_in_init) | |
150 | return; | |
151 | ||
1a60d4c5 | 152 | mutex_lock(&ops_mutex); |
9244b2c3 | 153 | list_for_each_entry(ops, &opslist, list) { |
1da177e4 LT |
154 | if (! (ops->driver & DRIVER_LOADED) && |
155 | ! (ops->driver & DRIVER_REQUESTED)) { | |
156 | ops->used++; | |
1a60d4c5 | 157 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
158 | ops->driver |= DRIVER_REQUESTED; |
159 | request_module("snd-%s", ops->id); | |
1a60d4c5 | 160 | mutex_lock(&ops_mutex); |
1da177e4 LT |
161 | ops->used--; |
162 | } | |
163 | } | |
1a60d4c5 | 164 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
165 | #endif |
166 | } | |
167 | ||
168 | /* | |
169 | * register a sequencer device | |
170 | * card = card info (NULL allowed) | |
171 | * device = device number (if any) | |
172 | * id = id of driver | |
173 | * result = return pointer (NULL allowed if unnecessary) | |
174 | */ | |
c7e0b5bf TI |
175 | int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize, |
176 | struct snd_seq_device **result) | |
1da177e4 | 177 | { |
c7e0b5bf TI |
178 | struct snd_seq_device *dev; |
179 | struct ops_list *ops; | |
1da177e4 | 180 | int err; |
c7e0b5bf | 181 | static struct snd_device_ops dops = { |
1da177e4 LT |
182 | .dev_free = snd_seq_device_dev_free, |
183 | .dev_register = snd_seq_device_dev_register, | |
184 | .dev_disconnect = snd_seq_device_dev_disconnect, | |
1da177e4 LT |
185 | }; |
186 | ||
187 | if (result) | |
188 | *result = NULL; | |
189 | ||
190 | snd_assert(id != NULL, return -EINVAL); | |
191 | ||
192 | ops = find_driver(id, 1); | |
193 | if (ops == NULL) | |
194 | return -ENOMEM; | |
195 | ||
ecca82b4 | 196 | dev = kzalloc(sizeof(*dev)*2 + argsize, GFP_KERNEL); |
1da177e4 LT |
197 | if (dev == NULL) { |
198 | unlock_driver(ops); | |
199 | return -ENOMEM; | |
200 | } | |
201 | ||
202 | /* set up device info */ | |
203 | dev->card = card; | |
204 | dev->device = device; | |
205 | strlcpy(dev->id, id, sizeof(dev->id)); | |
206 | dev->argsize = argsize; | |
207 | dev->status = SNDRV_SEQ_DEVICE_FREE; | |
208 | ||
209 | /* add this device to the list */ | |
1a60d4c5 | 210 | mutex_lock(&ops->reg_mutex); |
1da177e4 LT |
211 | list_add_tail(&dev->list, &ops->dev_list); |
212 | ops->num_devices++; | |
1a60d4c5 | 213 | mutex_unlock(&ops->reg_mutex); |
1da177e4 LT |
214 | |
215 | unlock_driver(ops); | |
216 | ||
217 | if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) { | |
218 | snd_seq_device_free(dev); | |
219 | return err; | |
220 | } | |
221 | ||
222 | if (result) | |
223 | *result = dev; | |
224 | ||
225 | return 0; | |
226 | } | |
227 | ||
228 | /* | |
229 | * free the existing device | |
230 | */ | |
c7e0b5bf | 231 | static int snd_seq_device_free(struct snd_seq_device *dev) |
1da177e4 | 232 | { |
c7e0b5bf | 233 | struct ops_list *ops; |
1da177e4 LT |
234 | |
235 | snd_assert(dev != NULL, return -EINVAL); | |
236 | ||
237 | ops = find_driver(dev->id, 0); | |
238 | if (ops == NULL) | |
239 | return -ENXIO; | |
240 | ||
241 | /* remove the device from the list */ | |
1a60d4c5 | 242 | mutex_lock(&ops->reg_mutex); |
1da177e4 LT |
243 | list_del(&dev->list); |
244 | ops->num_devices--; | |
1a60d4c5 | 245 | mutex_unlock(&ops->reg_mutex); |
1da177e4 LT |
246 | |
247 | free_device(dev, ops); | |
248 | if (dev->private_free) | |
249 | dev->private_free(dev); | |
250 | kfree(dev); | |
251 | ||
252 | unlock_driver(ops); | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
c7e0b5bf | 257 | static int snd_seq_device_dev_free(struct snd_device *device) |
1da177e4 | 258 | { |
c7e0b5bf | 259 | struct snd_seq_device *dev = device->device_data; |
1da177e4 LT |
260 | return snd_seq_device_free(dev); |
261 | } | |
262 | ||
263 | /* | |
264 | * register the device | |
265 | */ | |
c7e0b5bf | 266 | static int snd_seq_device_dev_register(struct snd_device *device) |
1da177e4 | 267 | { |
c7e0b5bf TI |
268 | struct snd_seq_device *dev = device->device_data; |
269 | struct ops_list *ops; | |
1da177e4 LT |
270 | |
271 | ops = find_driver(dev->id, 0); | |
272 | if (ops == NULL) | |
273 | return -ENOENT; | |
274 | ||
275 | /* initialize this device if the corresponding driver was | |
276 | * already loaded | |
277 | */ | |
278 | if (ops->driver & DRIVER_LOADED) | |
279 | init_device(dev, ops); | |
280 | ||
281 | unlock_driver(ops); | |
282 | return 0; | |
283 | } | |
284 | ||
285 | /* | |
286 | * disconnect the device | |
287 | */ | |
c7e0b5bf | 288 | static int snd_seq_device_dev_disconnect(struct snd_device *device) |
1da177e4 | 289 | { |
c7e0b5bf TI |
290 | struct snd_seq_device *dev = device->device_data; |
291 | struct ops_list *ops; | |
1da177e4 LT |
292 | |
293 | ops = find_driver(dev->id, 0); | |
294 | if (ops == NULL) | |
295 | return -ENOENT; | |
296 | ||
297 | free_device(dev, ops); | |
298 | ||
299 | unlock_driver(ops); | |
300 | return 0; | |
301 | } | |
302 | ||
1da177e4 LT |
303 | /* |
304 | * register device driver | |
305 | * id = driver id | |
306 | * entry = driver operators - duplicated to each instance | |
307 | */ | |
c7e0b5bf TI |
308 | int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry, |
309 | int argsize) | |
1da177e4 | 310 | { |
c7e0b5bf | 311 | struct ops_list *ops; |
9244b2c3 | 312 | struct snd_seq_device *dev; |
1da177e4 LT |
313 | |
314 | if (id == NULL || entry == NULL || | |
315 | entry->init_device == NULL || entry->free_device == NULL) | |
316 | return -EINVAL; | |
317 | ||
318 | snd_seq_autoload_lock(); | |
319 | ops = find_driver(id, 1); | |
320 | if (ops == NULL) { | |
321 | snd_seq_autoload_unlock(); | |
322 | return -ENOMEM; | |
323 | } | |
324 | if (ops->driver & DRIVER_LOADED) { | |
325 | snd_printk(KERN_WARNING "driver_register: driver '%s' already exists\n", id); | |
326 | unlock_driver(ops); | |
327 | snd_seq_autoload_unlock(); | |
328 | return -EBUSY; | |
329 | } | |
330 | ||
1a60d4c5 | 331 | mutex_lock(&ops->reg_mutex); |
1da177e4 LT |
332 | /* copy driver operators */ |
333 | ops->ops = *entry; | |
334 | ops->driver |= DRIVER_LOADED; | |
335 | ops->argsize = argsize; | |
336 | ||
337 | /* initialize existing devices if necessary */ | |
9244b2c3 | 338 | list_for_each_entry(dev, &ops->dev_list, list) { |
1da177e4 LT |
339 | init_device(dev, ops); |
340 | } | |
1a60d4c5 | 341 | mutex_unlock(&ops->reg_mutex); |
1da177e4 LT |
342 | |
343 | unlock_driver(ops); | |
344 | snd_seq_autoload_unlock(); | |
345 | ||
346 | return 0; | |
347 | } | |
348 | ||
349 | ||
350 | /* | |
351 | * create driver record | |
352 | */ | |
c7e0b5bf | 353 | static struct ops_list * create_driver(char *id) |
1da177e4 | 354 | { |
c7e0b5bf | 355 | struct ops_list *ops; |
1da177e4 | 356 | |
59feddb2 | 357 | ops = kzalloc(sizeof(*ops), GFP_KERNEL); |
1da177e4 LT |
358 | if (ops == NULL) |
359 | return ops; | |
1da177e4 LT |
360 | |
361 | /* set up driver entry */ | |
362 | strlcpy(ops->id, id, sizeof(ops->id)); | |
1a60d4c5 | 363 | mutex_init(&ops->reg_mutex); |
933a2efc AV |
364 | /* |
365 | * The ->reg_mutex locking rules are per-driver, so we create | |
366 | * separate per-driver lock classes: | |
367 | */ | |
368 | lockdep_set_class(&ops->reg_mutex, (struct lock_class_key *)id); | |
369 | ||
1da177e4 LT |
370 | ops->driver = DRIVER_EMPTY; |
371 | INIT_LIST_HEAD(&ops->dev_list); | |
372 | /* lock this instance */ | |
373 | ops->used = 1; | |
374 | ||
375 | /* register driver entry */ | |
1a60d4c5 | 376 | mutex_lock(&ops_mutex); |
1da177e4 LT |
377 | list_add_tail(&ops->list, &opslist); |
378 | num_ops++; | |
1a60d4c5 | 379 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
380 | |
381 | return ops; | |
382 | } | |
383 | ||
384 | ||
385 | /* | |
386 | * unregister the specified driver | |
387 | */ | |
388 | int snd_seq_device_unregister_driver(char *id) | |
389 | { | |
c7e0b5bf | 390 | struct ops_list *ops; |
9244b2c3 | 391 | struct snd_seq_device *dev; |
1da177e4 LT |
392 | |
393 | ops = find_driver(id, 0); | |
394 | if (ops == NULL) | |
395 | return -ENXIO; | |
396 | if (! (ops->driver & DRIVER_LOADED) || | |
397 | (ops->driver & DRIVER_LOCKED)) { | |
c7e0b5bf TI |
398 | snd_printk(KERN_ERR "driver_unregister: cannot unload driver '%s': status=%x\n", |
399 | id, ops->driver); | |
1da177e4 LT |
400 | unlock_driver(ops); |
401 | return -EBUSY; | |
402 | } | |
403 | ||
404 | /* close and release all devices associated with this driver */ | |
1a60d4c5 | 405 | mutex_lock(&ops->reg_mutex); |
1da177e4 | 406 | ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */ |
9244b2c3 | 407 | list_for_each_entry(dev, &ops->dev_list, list) { |
1da177e4 LT |
408 | free_device(dev, ops); |
409 | } | |
410 | ||
411 | ops->driver = 0; | |
412 | if (ops->num_init_devices > 0) | |
c7e0b5bf TI |
413 | snd_printk(KERN_ERR "free_driver: init_devices > 0!! (%d)\n", |
414 | ops->num_init_devices); | |
1a60d4c5 | 415 | mutex_unlock(&ops->reg_mutex); |
1da177e4 LT |
416 | |
417 | unlock_driver(ops); | |
418 | ||
419 | /* remove empty driver entries */ | |
420 | remove_drivers(); | |
421 | ||
422 | return 0; | |
423 | } | |
424 | ||
425 | ||
426 | /* | |
427 | * remove empty driver entries | |
428 | */ | |
429 | static void remove_drivers(void) | |
430 | { | |
431 | struct list_head *head; | |
432 | ||
1a60d4c5 | 433 | mutex_lock(&ops_mutex); |
1da177e4 LT |
434 | head = opslist.next; |
435 | while (head != &opslist) { | |
c7e0b5bf | 436 | struct ops_list *ops = list_entry(head, struct ops_list, list); |
1da177e4 LT |
437 | if (! (ops->driver & DRIVER_LOADED) && |
438 | ops->used == 0 && ops->num_devices == 0) { | |
439 | head = head->next; | |
440 | list_del(&ops->list); | |
441 | kfree(ops); | |
442 | num_ops--; | |
443 | } else | |
444 | head = head->next; | |
445 | } | |
1a60d4c5 | 446 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
447 | } |
448 | ||
449 | /* | |
450 | * initialize the device - call init_device operator | |
451 | */ | |
c7e0b5bf | 452 | static int init_device(struct snd_seq_device *dev, struct ops_list *ops) |
1da177e4 LT |
453 | { |
454 | if (! (ops->driver & DRIVER_LOADED)) | |
455 | return 0; /* driver is not loaded yet */ | |
456 | if (dev->status != SNDRV_SEQ_DEVICE_FREE) | |
457 | return 0; /* already initialized */ | |
458 | if (ops->argsize != dev->argsize) { | |
c7e0b5bf TI |
459 | snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", |
460 | dev->name, ops->id, ops->argsize, dev->argsize); | |
1da177e4 LT |
461 | return -EINVAL; |
462 | } | |
463 | if (ops->ops.init_device(dev) >= 0) { | |
464 | dev->status = SNDRV_SEQ_DEVICE_REGISTERED; | |
465 | ops->num_init_devices++; | |
466 | } else { | |
c7e0b5bf TI |
467 | snd_printk(KERN_ERR "init_device failed: %s: %s\n", |
468 | dev->name, dev->id); | |
1da177e4 LT |
469 | } |
470 | ||
471 | return 0; | |
472 | } | |
473 | ||
474 | /* | |
475 | * release the device - call free_device operator | |
476 | */ | |
c7e0b5bf | 477 | static int free_device(struct snd_seq_device *dev, struct ops_list *ops) |
1da177e4 LT |
478 | { |
479 | int result; | |
480 | ||
481 | if (! (ops->driver & DRIVER_LOADED)) | |
482 | return 0; /* driver is not loaded yet */ | |
483 | if (dev->status != SNDRV_SEQ_DEVICE_REGISTERED) | |
484 | return 0; /* not registered */ | |
485 | if (ops->argsize != dev->argsize) { | |
c7e0b5bf TI |
486 | snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", |
487 | dev->name, ops->id, ops->argsize, dev->argsize); | |
1da177e4 LT |
488 | return -EINVAL; |
489 | } | |
490 | if ((result = ops->ops.free_device(dev)) >= 0 || result == -ENXIO) { | |
491 | dev->status = SNDRV_SEQ_DEVICE_FREE; | |
492 | dev->driver_data = NULL; | |
493 | ops->num_init_devices--; | |
494 | } else { | |
c7e0b5bf TI |
495 | snd_printk(KERN_ERR "free_device failed: %s: %s\n", |
496 | dev->name, dev->id); | |
1da177e4 LT |
497 | } |
498 | ||
499 | return 0; | |
500 | } | |
501 | ||
502 | /* | |
503 | * find the matching driver with given id | |
504 | */ | |
c7e0b5bf | 505 | static struct ops_list * find_driver(char *id, int create_if_empty) |
1da177e4 | 506 | { |
9244b2c3 | 507 | struct ops_list *ops; |
1da177e4 | 508 | |
1a60d4c5 | 509 | mutex_lock(&ops_mutex); |
9244b2c3 | 510 | list_for_each_entry(ops, &opslist, list) { |
1da177e4 LT |
511 | if (strcmp(ops->id, id) == 0) { |
512 | ops->used++; | |
1a60d4c5 | 513 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
514 | return ops; |
515 | } | |
516 | } | |
1a60d4c5 | 517 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
518 | if (create_if_empty) |
519 | return create_driver(id); | |
520 | return NULL; | |
521 | } | |
522 | ||
c7e0b5bf | 523 | static void unlock_driver(struct ops_list *ops) |
1da177e4 | 524 | { |
1a60d4c5 | 525 | mutex_lock(&ops_mutex); |
1da177e4 | 526 | ops->used--; |
1a60d4c5 | 527 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
528 | } |
529 | ||
530 | ||
531 | /* | |
532 | * module part | |
533 | */ | |
534 | ||
535 | static int __init alsa_seq_device_init(void) | |
536 | { | |
04f141a8 | 537 | #ifdef CONFIG_PROC_FS |
c7e0b5bf TI |
538 | info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", |
539 | snd_seq_root); | |
1da177e4 LT |
540 | if (info_entry == NULL) |
541 | return -ENOMEM; | |
542 | info_entry->content = SNDRV_INFO_CONTENT_TEXT; | |
1da177e4 LT |
543 | info_entry->c.text.read = snd_seq_device_info; |
544 | if (snd_info_register(info_entry) < 0) { | |
545 | snd_info_free_entry(info_entry); | |
546 | return -ENOMEM; | |
547 | } | |
04f141a8 | 548 | #endif |
1da177e4 LT |
549 | return 0; |
550 | } | |
551 | ||
552 | static void __exit alsa_seq_device_exit(void) | |
553 | { | |
554 | remove_drivers(); | |
04f141a8 | 555 | #ifdef CONFIG_PROC_FS |
746d4a02 | 556 | snd_info_free_entry(info_entry); |
04f141a8 | 557 | #endif |
1da177e4 LT |
558 | if (num_ops) |
559 | snd_printk(KERN_ERR "drivers not released (%d)\n", num_ops); | |
560 | } | |
561 | ||
562 | module_init(alsa_seq_device_init) | |
563 | module_exit(alsa_seq_device_exit) | |
564 | ||
565 | EXPORT_SYMBOL(snd_seq_device_load_drivers); | |
566 | EXPORT_SYMBOL(snd_seq_device_new); | |
567 | EXPORT_SYMBOL(snd_seq_device_register_driver); | |
568 | EXPORT_SYMBOL(snd_seq_device_unregister_driver); | |
1da177e4 LT |
569 | EXPORT_SYMBOL(snd_seq_autoload_lock); |
570 | EXPORT_SYMBOL(snd_seq_autoload_unlock); |