| 1 | |
| 2 | |
| 3 | * ALSA sequencer device management
|
| 4 | * Copyright (c) 1999 by Takashi Iwai <[email protected]>
|
| 5 | *
|
| 6 | *----------------------------------------------------------------
|
| 7 | *
|
| 8 | * This device handler separates the card driver module from sequencer
|
| 9 | * stuff (sequencer core, synth drivers, etc), so that user can avoid
|
| 10 | * to spend unnecessary resources e.g. if he needs only listening to
|
| 11 | * MP3s.
|
| 12 | *
|
| 13 | * The card (or lowlevel) driver creates a sequencer device entry
|
| 14 | * via snd_seq_device_new(). This is an entry pointer to communicate
|
| 15 | * with the sequencer device "driver", which is involved with the
|
| 16 | * actual part to communicate with the sequencer core.
|
| 17 | * Each sequencer device entry has an id string and the corresponding
|
| 18 | * driver with the same id is loaded when required. For example,
|
| 19 | * lowlevel codes to access emu8000 chip on sbawe card are included in
|
| 20 | * emu8000-synth module. To activate this module, the hardware
|
| 21 | * resources like i/o port are passed via snd_seq_device argument.
|
| 22 |
|
| 23 |
|
| 24 | #include <linux/device.h>
|
| 25 | #include <linux/init.h>
|
| 26 | #include <linux/module.h>
|
| 27 | #include <sound/core.h>
|
| 28 | #include <sound/info.h>
|
| 29 | #include <sound/seq_device.h>
|
| 30 | #include <sound/seq_kernel.h>
|
| 31 | #include <sound/initval.h>
|
| 32 | #include <linux/kmod.h>
|
| 33 | #include <linux/slab.h>
|
| 34 | #include <linux/mutex.h>
|
| 35 |
|
| 36 | MODULE_AUTHOR("Takashi Iwai <[email protected]>");
|
| 37 | MODULE_DESCRIPTION("ALSA sequencer device management");
|
| 38 | MODULE_LICENSE("GPL");
|
| 39 |
|
| 40 | |
| 41 | * bus definition
|
| 42 |
|
| 43 | static int snd_seq_bus_match(struct device *dev, const struct device_driver *drv)
|
| 44 | {
|
| 45 | struct snd_seq_device *sdev = to_seq_dev(dev);
|
| 46 | const struct snd_seq_driver *sdrv = to_seq_drv(drv);
|
| 47 |
|
| 48 | return strcmp(sdrv->id, sdev->id) == 0 &&
|
| 49 | sdrv->argsize == sdev->argsize;
|
| 50 | }
|
| 51 |
|
| 52 | static int snd_seq_bus_probe(struct device *dev)
|
| 53 | {
|
| 54 | struct snd_seq_device *sdev = to_seq_dev(dev);
|
| 55 | const struct snd_seq_driver *sdrv = to_seq_drv(dev->driver);
|
| 56 |
|
| 57 | if (sdrv->probe)
|
| 58 | return sdrv->probe(sdev);
|
| 59 | else
|
| 60 | return 0;
|
| 61 | }
|
| 62 |
|
| 63 | static void snd_seq_bus_remove(struct device *dev)
|
| 64 | {
|
| 65 | struct snd_seq_device *sdev = to_seq_dev(dev);
|
| 66 | const struct snd_seq_driver *sdrv = to_seq_drv(dev->driver);
|
| 67 |
|
| 68 | if (sdrv->remove)
|
| 69 | sdrv->remove(sdev);
|
| 70 | }
|
| 71 |
|
| 72 | static const struct bus_type snd_seq_bus_type = {
|
| 73 | .name = "snd_seq",
|
| 74 | .match = snd_seq_bus_match,
|
| 75 | .probe = snd_seq_bus_probe,
|
| 76 | .remove = snd_seq_bus_remove,
|
| 77 | };
|
| 78 |
|
| 79 | |
| 80 | * proc interface -- just for compatibility
|
| 81 |
|
| 82 | #ifdef CONFIG_SND_PROC_FS
|
| 83 | static struct snd_info_entry *info_entry;
|
| 84 |
|
| 85 | static int print_dev_info(struct device *dev, void *data)
|
| 86 | {
|
| 87 | struct snd_seq_device *sdev = to_seq_dev(dev);
|
| 88 | struct snd_info_buffer *buffer = data;
|
| 89 |
|
| 90 | snd_iprintf(buffer, "snd-%s,%s,%d\n", sdev->id,
|
| 91 | dev->driver ? "loaded" : "empty",
|
| 92 | dev->driver ? 1 : 0);
|
| 93 | return 0;
|
| 94 | }
|
| 95 |
|
| 96 | static void snd_seq_device_info(struct snd_info_entry *entry,
|
| 97 | struct snd_info_buffer *buffer)
|
| 98 | {
|
| 99 | bus_for_each_dev(&snd_seq_bus_type, NULL, buffer, print_dev_info);
|
| 100 | }
|
| 101 | #endif
|
| 102 |
|
| 103 | |
| 104 | * load all registered drivers (called from seq_clientmgr.c)
|
| 105 |
|
| 106 |
|
| 107 | #ifdef CONFIG_MODULES
|
| 108 |
|
| 109 | static atomic_t snd_seq_in_init = ATOMIC_INIT(1);
|
| 110 |
|
| 111 | static int request_seq_drv(struct device *dev, void *data)
|
| 112 | {
|
| 113 | struct snd_seq_device *sdev = to_seq_dev(dev);
|
| 114 |
|
| 115 | if (!dev->driver)
|
| 116 | request_module("snd-%s", sdev->id);
|
| 117 | return 0;
|
| 118 | }
|
| 119 |
|
| 120 | static void autoload_drivers(struct work_struct *work)
|
| 121 | {
|
| 122 |
|
| 123 | if (atomic_inc_return(&snd_seq_in_init) == 1)
|
| 124 | bus_for_each_dev(&snd_seq_bus_type, NULL, NULL,
|
| 125 | request_seq_drv);
|
| 126 | atomic_dec(&snd_seq_in_init);
|
| 127 | }
|
| 128 |
|
| 129 | static DECLARE_WORK(autoload_work, autoload_drivers);
|
| 130 |
|
| 131 | static void queue_autoload_drivers(void)
|
| 132 | {
|
| 133 | schedule_work(&autoload_work);
|
| 134 | }
|
| 135 |
|
| 136 | void snd_seq_autoload_init(void)
|
| 137 | {
|
| 138 | atomic_dec(&snd_seq_in_init);
|
| 139 | #ifdef CONFIG_SND_SEQUENCER_MODULE
|
| 140 |
|
| 141 | queue_autoload_drivers();
|
| 142 | #endif
|
| 143 | }
|
| 144 | EXPORT_SYMBOL(snd_seq_autoload_init);
|
| 145 |
|
| 146 | void snd_seq_autoload_exit(void)
|
| 147 | {
|
| 148 | atomic_inc(&snd_seq_in_init);
|
| 149 | }
|
| 150 | EXPORT_SYMBOL(snd_seq_autoload_exit);
|
| 151 |
|
| 152 | void snd_seq_device_load_drivers(void)
|
| 153 | {
|
| 154 | queue_autoload_drivers();
|
| 155 | flush_work(&autoload_work);
|
| 156 | }
|
| 157 | EXPORT_SYMBOL(snd_seq_device_load_drivers);
|
| 158 |
|
| 159 | static inline void cancel_autoload_drivers(void)
|
| 160 | {
|
| 161 | cancel_work_sync(&autoload_work);
|
| 162 | }
|
| 163 | #else
|
| 164 | static inline void queue_autoload_drivers(void)
|
| 165 | {
|
| 166 | }
|
| 167 |
|
| 168 | static inline void cancel_autoload_drivers(void)
|
| 169 | {
|
| 170 | }
|
| 171 | #endif
|
| 172 |
|
| 173 | |
| 174 | * device management
|
| 175 |
|
| 176 | static int snd_seq_device_dev_free(struct snd_device *device)
|
| 177 | {
|
| 178 | struct snd_seq_device *dev = device->device_data;
|
| 179 |
|
| 180 | cancel_autoload_drivers();
|
| 181 | if (dev->private_free)
|
| 182 | dev->private_free(dev);
|
| 183 | put_device(&dev->dev);
|
| 184 | return 0;
|
| 185 | }
|
| 186 |
|
| 187 | static int snd_seq_device_dev_register(struct snd_device *device)
|
| 188 | {
|
| 189 | struct snd_seq_device *dev = device->device_data;
|
| 190 | int err;
|
| 191 |
|
| 192 | err = device_add(&dev->dev);
|
| 193 | if (err < 0)
|
| 194 | return err;
|
| 195 | if (!dev->dev.driver)
|
| 196 | queue_autoload_drivers();
|
| 197 | return 0;
|
| 198 | }
|
| 199 |
|
| 200 | static int snd_seq_device_dev_disconnect(struct snd_device *device)
|
| 201 | {
|
| 202 | struct snd_seq_device *dev = device->device_data;
|
| 203 |
|
| 204 | device_del(&dev->dev);
|
| 205 | return 0;
|
| 206 | }
|
| 207 |
|
| 208 | static void snd_seq_dev_release(struct device *dev)
|
| 209 | {
|
| 210 | kfree(to_seq_dev(dev));
|
| 211 | }
|
| 212 |
|
| 213 | |
| 214 | * register a sequencer device
|
| 215 | * card = card info
|
| 216 | * device = device number (if any)
|
| 217 | * id = id of driver
|
| 218 | * result = return pointer (NULL allowed if unnecessary)
|
| 219 |
|
| 220 | int snd_seq_device_new(struct snd_card *card, int device, const char *id,
|
| 221 | int argsize, struct snd_seq_device **result)
|
| 222 | {
|
| 223 | struct snd_seq_device *dev;
|
| 224 | int err;
|
| 225 | static const struct snd_device_ops dops = {
|
| 226 | .dev_free = snd_seq_device_dev_free,
|
| 227 | .dev_register = snd_seq_device_dev_register,
|
| 228 | .dev_disconnect = snd_seq_device_dev_disconnect,
|
| 229 | };
|
| 230 |
|
| 231 | if (result)
|
| 232 | *result = NULL;
|
| 233 |
|
| 234 | if (snd_BUG_ON(!id))
|
| 235 | return -EINVAL;
|
| 236 |
|
| 237 | if (argsize < 0)
|
| 238 | return -EINVAL;
|
| 239 |
|
| 240 | dev = kzalloc_flex(*dev, args, argsize);
|
| 241 | if (!dev)
|
| 242 | return -ENOMEM;
|
| 243 |
|
| 244 |
|
| 245 | dev->card = card;
|
| 246 | dev->device = device;
|
| 247 | dev->id = id;
|
| 248 | dev->argsize = argsize;
|
| 249 |
|
| 250 | device_initialize(&dev->dev);
|
| 251 | dev->dev.parent = &card->card_dev;
|
| 252 | dev->dev.bus = &snd_seq_bus_type;
|
| 253 | dev->dev.release = snd_seq_dev_release;
|
| 254 | dev_set_name(&dev->dev, "%s-%d-%d", dev->id, card->number, device);
|
| 255 |
|
| 256 |
|
| 257 | err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops);
|
| 258 | if (err < 0) {
|
| 259 | put_device(&dev->dev);
|
| 260 | return err;
|
| 261 | }
|
| 262 |
|
| 263 | if (result)
|
| 264 | *result = dev;
|
| 265 |
|
| 266 | return 0;
|
| 267 | }
|
| 268 | EXPORT_SYMBOL(snd_seq_device_new);
|
| 269 |
|
| 270 | |
| 271 | * driver registration
|
| 272 |
|
| 273 | int __snd_seq_driver_register(struct snd_seq_driver *drv, struct module *mod)
|
| 274 | {
|
| 275 | if (WARN_ON(!drv->driver.name || !drv->id || drv->driver.probe || drv->driver.remove))
|
| 276 | return -EINVAL;
|
| 277 |
|
| 278 | drv->driver.bus = &snd_seq_bus_type;
|
| 279 | drv->driver.owner = mod;
|
| 280 |
|
| 281 | return driver_register(&drv->driver);
|
| 282 | }
|
| 283 | EXPORT_SYMBOL_GPL(__snd_seq_driver_register);
|
| 284 |
|
| 285 | void snd_seq_driver_unregister(struct snd_seq_driver *drv)
|
| 286 | {
|
| 287 | driver_unregister(&drv->driver);
|
| 288 | }
|
| 289 | EXPORT_SYMBOL_GPL(snd_seq_driver_unregister);
|
| 290 |
|
| 291 | |
| 292 | * module part
|
| 293 |
|
| 294 |
|
| 295 | static int __init seq_dev_proc_init(void)
|
| 296 | {
|
| 297 | #ifdef CONFIG_SND_PROC_FS
|
| 298 | info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers",
|
| 299 | snd_seq_root);
|
| 300 | if (info_entry == NULL)
|
| 301 | return -ENOMEM;
|
| 302 | info_entry->content = SNDRV_INFO_CONTENT_TEXT;
|
| 303 | info_entry->c.text.read = snd_seq_device_info;
|
| 304 | if (snd_info_register(info_entry) < 0) {
|
| 305 | snd_info_free_entry(info_entry);
|
| 306 | return -ENOMEM;
|
| 307 | }
|
| 308 | #endif
|
| 309 | return 0;
|
| 310 | }
|
| 311 |
|
| 312 | static int __init alsa_seq_device_init(void)
|
| 313 | {
|
| 314 | int err;
|
| 315 |
|
| 316 | err = bus_register(&snd_seq_bus_type);
|
| 317 | if (err < 0)
|
| 318 | return err;
|
| 319 | err = seq_dev_proc_init();
|
| 320 | if (err < 0)
|
| 321 | bus_unregister(&snd_seq_bus_type);
|
| 322 | return err;
|
| 323 | }
|
| 324 |
|
| 325 | static void __exit alsa_seq_device_exit(void)
|
| 326 | {
|
| 327 | #ifdef CONFIG_MODULES
|
| 328 | cancel_work_sync(&autoload_work);
|
| 329 | #endif
|
| 330 | #ifdef CONFIG_SND_PROC_FS
|
| 331 | snd_info_free_entry(info_entry);
|
| 332 | #endif
|
| 333 | bus_unregister(&snd_seq_bus_type);
|
| 334 | }
|
| 335 |
|
| 336 | subsys_initcall(alsa_seq_device_init)
|
| 337 | module_exit(alsa_seq_device_exit)
|