]>
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 | ||
53 | /* | |
54 | * driver list | |
55 | */ | |
56 | typedef struct ops_list ops_list_t; | |
57 | ||
58 | /* driver state */ | |
59 | #define DRIVER_EMPTY 0 | |
60 | #define DRIVER_LOADED (1<<0) | |
61 | #define DRIVER_REQUESTED (1<<1) | |
62 | #define DRIVER_LOCKED (1<<2) | |
63 | ||
64 | struct ops_list { | |
65 | char id[ID_LEN]; /* driver id */ | |
66 | int driver; /* driver state */ | |
67 | int used; /* reference counter */ | |
68 | int argsize; /* argument size */ | |
69 | ||
70 | /* operators */ | |
71 | snd_seq_dev_ops_t ops; | |
72 | ||
73 | /* registred devices */ | |
74 | struct list_head dev_list; /* list of devices */ | |
75 | int num_devices; /* number of associated devices */ | |
76 | int num_init_devices; /* number of initialized devices */ | |
77 | struct semaphore reg_mutex; | |
78 | ||
79 | struct list_head list; /* next driver */ | |
80 | }; | |
81 | ||
82 | ||
83 | static LIST_HEAD(opslist); | |
84 | static int num_ops; | |
85 | static DECLARE_MUTEX(ops_mutex); | |
86 | static snd_info_entry_t *info_entry = NULL; | |
87 | ||
88 | /* | |
89 | * prototypes | |
90 | */ | |
91 | static int snd_seq_device_free(snd_seq_device_t *dev); | |
92 | static int snd_seq_device_dev_free(snd_device_t *device); | |
93 | static int snd_seq_device_dev_register(snd_device_t *device); | |
94 | static int snd_seq_device_dev_disconnect(snd_device_t *device); | |
95 | static int snd_seq_device_dev_unregister(snd_device_t *device); | |
96 | ||
97 | static int init_device(snd_seq_device_t *dev, ops_list_t *ops); | |
98 | static int free_device(snd_seq_device_t *dev, ops_list_t *ops); | |
99 | static ops_list_t *find_driver(char *id, int create_if_empty); | |
100 | static ops_list_t *create_driver(char *id); | |
101 | static void unlock_driver(ops_list_t *ops); | |
102 | static void remove_drivers(void); | |
103 | ||
104 | /* | |
105 | * show all drivers and their status | |
106 | */ | |
107 | ||
108 | static void snd_seq_device_info(snd_info_entry_t *entry, snd_info_buffer_t * buffer) | |
109 | { | |
110 | struct list_head *head; | |
111 | ||
112 | down(&ops_mutex); | |
113 | list_for_each(head, &opslist) { | |
114 | ops_list_t *ops = list_entry(head, ops_list_t, list); | |
115 | snd_iprintf(buffer, "snd-%s%s%s%s,%d\n", | |
116 | ops->id, | |
117 | ops->driver & DRIVER_LOADED ? ",loaded" : (ops->driver == DRIVER_EMPTY ? ",empty" : ""), | |
118 | ops->driver & DRIVER_REQUESTED ? ",requested" : "", | |
119 | ops->driver & DRIVER_LOCKED ? ",locked" : "", | |
120 | ops->num_devices); | |
121 | } | |
122 | up(&ops_mutex); | |
123 | } | |
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) { | |
159 | ops_list_t *ops = list_entry(head, ops_list_t, list); | |
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 | */ | |
181 | int snd_seq_device_new(snd_card_t *card, int device, char *id, int argsize, | |
182 | snd_seq_device_t **result) | |
183 | { | |
184 | snd_seq_device_t *dev; | |
185 | ops_list_t *ops; | |
186 | int err; | |
187 | static snd_device_ops_t dops = { | |
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 | ||
203 | dev = kcalloc(1, sizeof(*dev)*2 + argsize, GFP_KERNEL); | |
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 | */ | |
238 | static int snd_seq_device_free(snd_seq_device_t *dev) | |
239 | { | |
240 | ops_list_t *ops; | |
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 | ||
264 | static int snd_seq_device_dev_free(snd_device_t *device) | |
265 | { | |
266 | snd_seq_device_t *dev = device->device_data; | |
267 | return snd_seq_device_free(dev); | |
268 | } | |
269 | ||
270 | /* | |
271 | * register the device | |
272 | */ | |
273 | static int snd_seq_device_dev_register(snd_device_t *device) | |
274 | { | |
275 | snd_seq_device_t *dev = device->device_data; | |
276 | ops_list_t *ops; | |
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 | */ | |
295 | static int snd_seq_device_dev_disconnect(snd_device_t *device) | |
296 | { | |
297 | snd_seq_device_t *dev = device->device_data; | |
298 | ops_list_t *ops; | |
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 | */ | |
313 | static int snd_seq_device_dev_unregister(snd_device_t *device) | |
314 | { | |
315 | snd_seq_device_t *dev = device->device_data; | |
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 | */ | |
324 | int snd_seq_device_register_driver(char *id, snd_seq_dev_ops_t *entry, int argsize) | |
325 | { | |
326 | struct list_head *head; | |
327 | ops_list_t *ops; | |
328 | ||
329 | if (id == NULL || entry == NULL || | |
330 | entry->init_device == NULL || entry->free_device == NULL) | |
331 | return -EINVAL; | |
332 | ||
333 | snd_seq_autoload_lock(); | |
334 | ops = find_driver(id, 1); | |
335 | if (ops == NULL) { | |
336 | snd_seq_autoload_unlock(); | |
337 | return -ENOMEM; | |
338 | } | |
339 | if (ops->driver & DRIVER_LOADED) { | |
340 | snd_printk(KERN_WARNING "driver_register: driver '%s' already exists\n", id); | |
341 | unlock_driver(ops); | |
342 | snd_seq_autoload_unlock(); | |
343 | return -EBUSY; | |
344 | } | |
345 | ||
346 | down(&ops->reg_mutex); | |
347 | /* copy driver operators */ | |
348 | ops->ops = *entry; | |
349 | ops->driver |= DRIVER_LOADED; | |
350 | ops->argsize = argsize; | |
351 | ||
352 | /* initialize existing devices if necessary */ | |
353 | list_for_each(head, &ops->dev_list) { | |
354 | snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list); | |
355 | init_device(dev, ops); | |
356 | } | |
357 | up(&ops->reg_mutex); | |
358 | ||
359 | unlock_driver(ops); | |
360 | snd_seq_autoload_unlock(); | |
361 | ||
362 | return 0; | |
363 | } | |
364 | ||
365 | ||
366 | /* | |
367 | * create driver record | |
368 | */ | |
369 | static ops_list_t * create_driver(char *id) | |
370 | { | |
371 | ops_list_t *ops; | |
372 | ||
373 | ops = kmalloc(sizeof(*ops), GFP_KERNEL); | |
374 | if (ops == NULL) | |
375 | return ops; | |
376 | memset(ops, 0, sizeof(*ops)); | |
377 | ||
378 | /* set up driver entry */ | |
379 | strlcpy(ops->id, id, sizeof(ops->id)); | |
380 | init_MUTEX(&ops->reg_mutex); | |
381 | ops->driver = DRIVER_EMPTY; | |
382 | INIT_LIST_HEAD(&ops->dev_list); | |
383 | /* lock this instance */ | |
384 | ops->used = 1; | |
385 | ||
386 | /* register driver entry */ | |
387 | down(&ops_mutex); | |
388 | list_add_tail(&ops->list, &opslist); | |
389 | num_ops++; | |
390 | up(&ops_mutex); | |
391 | ||
392 | return ops; | |
393 | } | |
394 | ||
395 | ||
396 | /* | |
397 | * unregister the specified driver | |
398 | */ | |
399 | int snd_seq_device_unregister_driver(char *id) | |
400 | { | |
401 | struct list_head *head; | |
402 | ops_list_t *ops; | |
403 | ||
404 | ops = find_driver(id, 0); | |
405 | if (ops == NULL) | |
406 | return -ENXIO; | |
407 | if (! (ops->driver & DRIVER_LOADED) || | |
408 | (ops->driver & DRIVER_LOCKED)) { | |
409 | snd_printk(KERN_ERR "driver_unregister: cannot unload driver '%s': status=%x\n", id, ops->driver); | |
410 | unlock_driver(ops); | |
411 | return -EBUSY; | |
412 | } | |
413 | ||
414 | /* close and release all devices associated with this driver */ | |
415 | down(&ops->reg_mutex); | |
416 | ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */ | |
417 | list_for_each(head, &ops->dev_list) { | |
418 | snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list); | |
419 | free_device(dev, ops); | |
420 | } | |
421 | ||
422 | ops->driver = 0; | |
423 | if (ops->num_init_devices > 0) | |
424 | snd_printk(KERN_ERR "free_driver: init_devices > 0!! (%d)\n", ops->num_init_devices); | |
425 | up(&ops->reg_mutex); | |
426 | ||
427 | unlock_driver(ops); | |
428 | ||
429 | /* remove empty driver entries */ | |
430 | remove_drivers(); | |
431 | ||
432 | return 0; | |
433 | } | |
434 | ||
435 | ||
436 | /* | |
437 | * remove empty driver entries | |
438 | */ | |
439 | static void remove_drivers(void) | |
440 | { | |
441 | struct list_head *head; | |
442 | ||
443 | down(&ops_mutex); | |
444 | head = opslist.next; | |
445 | while (head != &opslist) { | |
446 | ops_list_t *ops = list_entry(head, ops_list_t, list); | |
447 | if (! (ops->driver & DRIVER_LOADED) && | |
448 | ops->used == 0 && ops->num_devices == 0) { | |
449 | head = head->next; | |
450 | list_del(&ops->list); | |
451 | kfree(ops); | |
452 | num_ops--; | |
453 | } else | |
454 | head = head->next; | |
455 | } | |
456 | up(&ops_mutex); | |
457 | } | |
458 | ||
459 | /* | |
460 | * initialize the device - call init_device operator | |
461 | */ | |
462 | static int init_device(snd_seq_device_t *dev, ops_list_t *ops) | |
463 | { | |
464 | if (! (ops->driver & DRIVER_LOADED)) | |
465 | return 0; /* driver is not loaded yet */ | |
466 | if (dev->status != SNDRV_SEQ_DEVICE_FREE) | |
467 | return 0; /* already initialized */ | |
468 | if (ops->argsize != dev->argsize) { | |
469 | snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", dev->name, ops->id, ops->argsize, dev->argsize); | |
470 | return -EINVAL; | |
471 | } | |
472 | if (ops->ops.init_device(dev) >= 0) { | |
473 | dev->status = SNDRV_SEQ_DEVICE_REGISTERED; | |
474 | ops->num_init_devices++; | |
475 | } else { | |
476 | snd_printk(KERN_ERR "init_device failed: %s: %s\n", dev->name, dev->id); | |
477 | } | |
478 | ||
479 | return 0; | |
480 | } | |
481 | ||
482 | /* | |
483 | * release the device - call free_device operator | |
484 | */ | |
485 | static int free_device(snd_seq_device_t *dev, ops_list_t *ops) | |
486 | { | |
487 | int result; | |
488 | ||
489 | if (! (ops->driver & DRIVER_LOADED)) | |
490 | return 0; /* driver is not loaded yet */ | |
491 | if (dev->status != SNDRV_SEQ_DEVICE_REGISTERED) | |
492 | return 0; /* not registered */ | |
493 | if (ops->argsize != dev->argsize) { | |
494 | snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", dev->name, ops->id, ops->argsize, dev->argsize); | |
495 | return -EINVAL; | |
496 | } | |
497 | if ((result = ops->ops.free_device(dev)) >= 0 || result == -ENXIO) { | |
498 | dev->status = SNDRV_SEQ_DEVICE_FREE; | |
499 | dev->driver_data = NULL; | |
500 | ops->num_init_devices--; | |
501 | } else { | |
502 | snd_printk(KERN_ERR "free_device failed: %s: %s\n", dev->name, dev->id); | |
503 | } | |
504 | ||
505 | return 0; | |
506 | } | |
507 | ||
508 | /* | |
509 | * find the matching driver with given id | |
510 | */ | |
511 | static ops_list_t * find_driver(char *id, int create_if_empty) | |
512 | { | |
513 | struct list_head *head; | |
514 | ||
515 | down(&ops_mutex); | |
516 | list_for_each(head, &opslist) { | |
517 | ops_list_t *ops = list_entry(head, ops_list_t, list); | |
518 | if (strcmp(ops->id, id) == 0) { | |
519 | ops->used++; | |
520 | up(&ops_mutex); | |
521 | return ops; | |
522 | } | |
523 | } | |
524 | up(&ops_mutex); | |
525 | if (create_if_empty) | |
526 | return create_driver(id); | |
527 | return NULL; | |
528 | } | |
529 | ||
530 | static void unlock_driver(ops_list_t *ops) | |
531 | { | |
532 | down(&ops_mutex); | |
533 | ops->used--; | |
534 | up(&ops_mutex); | |
535 | } | |
536 | ||
537 | ||
538 | /* | |
539 | * module part | |
540 | */ | |
541 | ||
542 | static int __init alsa_seq_device_init(void) | |
543 | { | |
544 | info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", snd_seq_root); | |
545 | if (info_entry == NULL) | |
546 | return -ENOMEM; | |
547 | info_entry->content = SNDRV_INFO_CONTENT_TEXT; | |
548 | info_entry->c.text.read_size = 2048; | |
549 | info_entry->c.text.read = snd_seq_device_info; | |
550 | if (snd_info_register(info_entry) < 0) { | |
551 | snd_info_free_entry(info_entry); | |
552 | return -ENOMEM; | |
553 | } | |
554 | return 0; | |
555 | } | |
556 | ||
557 | static void __exit alsa_seq_device_exit(void) | |
558 | { | |
559 | remove_drivers(); | |
560 | snd_info_unregister(info_entry); | |
561 | if (num_ops) | |
562 | snd_printk(KERN_ERR "drivers not released (%d)\n", num_ops); | |
563 | } | |
564 | ||
565 | module_init(alsa_seq_device_init) | |
566 | module_exit(alsa_seq_device_exit) | |
567 | ||
568 | EXPORT_SYMBOL(snd_seq_device_load_drivers); | |
569 | EXPORT_SYMBOL(snd_seq_device_new); | |
570 | EXPORT_SYMBOL(snd_seq_device_register_driver); | |
571 | EXPORT_SYMBOL(snd_seq_device_unregister_driver); | |
572 | #ifdef CONFIG_KMOD | |
573 | EXPORT_SYMBOL(snd_seq_autoload_lock); | |
574 | EXPORT_SYMBOL(snd_seq_autoload_unlock); | |
575 | #endif |