18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ALSA sequencer device management 48c2ecf20Sopenharmony_ci * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci *---------------------------------------------------------------- 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * This device handler separates the card driver module from sequencer 98c2ecf20Sopenharmony_ci * stuff (sequencer core, synth drivers, etc), so that user can avoid 108c2ecf20Sopenharmony_ci * to spend unnecessary resources e.g. if he needs only listening to 118c2ecf20Sopenharmony_ci * MP3s. 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * The card (or lowlevel) driver creates a sequencer device entry 148c2ecf20Sopenharmony_ci * via snd_seq_device_new(). This is an entry pointer to communicate 158c2ecf20Sopenharmony_ci * with the sequencer device "driver", which is involved with the 168c2ecf20Sopenharmony_ci * actual part to communicate with the sequencer core. 178c2ecf20Sopenharmony_ci * Each sequencer device entry has an id string and the corresponding 188c2ecf20Sopenharmony_ci * driver with the same id is loaded when required. For example, 198c2ecf20Sopenharmony_ci * lowlevel codes to access emu8000 chip on sbawe card are included in 208c2ecf20Sopenharmony_ci * emu8000-synth module. To activate this module, the hardware 218c2ecf20Sopenharmony_ci * resources like i/o port are passed via snd_seq_device argument. 228c2ecf20Sopenharmony_ci */ 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include <linux/device.h> 258c2ecf20Sopenharmony_ci#include <linux/init.h> 268c2ecf20Sopenharmony_ci#include <linux/module.h> 278c2ecf20Sopenharmony_ci#include <sound/core.h> 288c2ecf20Sopenharmony_ci#include <sound/info.h> 298c2ecf20Sopenharmony_ci#include <sound/seq_device.h> 308c2ecf20Sopenharmony_ci#include <sound/seq_kernel.h> 318c2ecf20Sopenharmony_ci#include <sound/initval.h> 328c2ecf20Sopenharmony_ci#include <linux/kmod.h> 338c2ecf20Sopenharmony_ci#include <linux/slab.h> 348c2ecf20Sopenharmony_ci#include <linux/mutex.h> 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ciMODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); 378c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ALSA sequencer device management"); 388c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/* 418c2ecf20Sopenharmony_ci * bus definition 428c2ecf20Sopenharmony_ci */ 438c2ecf20Sopenharmony_cistatic int snd_seq_bus_match(struct device *dev, struct device_driver *drv) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci struct snd_seq_device *sdev = to_seq_dev(dev); 468c2ecf20Sopenharmony_ci struct snd_seq_driver *sdrv = to_seq_drv(drv); 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci return strcmp(sdrv->id, sdev->id) == 0 && 498c2ecf20Sopenharmony_ci sdrv->argsize == sdev->argsize; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic struct bus_type snd_seq_bus_type = { 538c2ecf20Sopenharmony_ci .name = "snd_seq", 548c2ecf20Sopenharmony_ci .match = snd_seq_bus_match, 558c2ecf20Sopenharmony_ci}; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci/* 588c2ecf20Sopenharmony_ci * proc interface -- just for compatibility 598c2ecf20Sopenharmony_ci */ 608c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_PROC_FS 618c2ecf20Sopenharmony_cistatic struct snd_info_entry *info_entry; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic int print_dev_info(struct device *dev, void *data) 648c2ecf20Sopenharmony_ci{ 658c2ecf20Sopenharmony_ci struct snd_seq_device *sdev = to_seq_dev(dev); 668c2ecf20Sopenharmony_ci struct snd_info_buffer *buffer = data; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci snd_iprintf(buffer, "snd-%s,%s,%d\n", sdev->id, 698c2ecf20Sopenharmony_ci dev->driver ? "loaded" : "empty", 708c2ecf20Sopenharmony_ci dev->driver ? 1 : 0); 718c2ecf20Sopenharmony_ci return 0; 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic void snd_seq_device_info(struct snd_info_entry *entry, 758c2ecf20Sopenharmony_ci struct snd_info_buffer *buffer) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci bus_for_each_dev(&snd_seq_bus_type, NULL, buffer, print_dev_info); 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci#endif 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci/* 828c2ecf20Sopenharmony_ci * load all registered drivers (called from seq_clientmgr.c) 838c2ecf20Sopenharmony_ci */ 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci#ifdef CONFIG_MODULES 868c2ecf20Sopenharmony_ci/* flag to block auto-loading */ 878c2ecf20Sopenharmony_cistatic atomic_t snd_seq_in_init = ATOMIC_INIT(1); /* blocked as default */ 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic int request_seq_drv(struct device *dev, void *data) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci struct snd_seq_device *sdev = to_seq_dev(dev); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci if (!dev->driver) 948c2ecf20Sopenharmony_ci request_module("snd-%s", sdev->id); 958c2ecf20Sopenharmony_ci return 0; 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic void autoload_drivers(struct work_struct *work) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci /* avoid reentrance */ 1018c2ecf20Sopenharmony_ci if (atomic_inc_return(&snd_seq_in_init) == 1) 1028c2ecf20Sopenharmony_ci bus_for_each_dev(&snd_seq_bus_type, NULL, NULL, 1038c2ecf20Sopenharmony_ci request_seq_drv); 1048c2ecf20Sopenharmony_ci atomic_dec(&snd_seq_in_init); 1058c2ecf20Sopenharmony_ci} 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_cistatic DECLARE_WORK(autoload_work, autoload_drivers); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistatic void queue_autoload_drivers(void) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci schedule_work(&autoload_work); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_civoid snd_seq_autoload_init(void) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci atomic_dec(&snd_seq_in_init); 1178c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_SEQUENCER_MODULE 1188c2ecf20Sopenharmony_ci /* initial autoload only when snd-seq is a module */ 1198c2ecf20Sopenharmony_ci queue_autoload_drivers(); 1208c2ecf20Sopenharmony_ci#endif 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_seq_autoload_init); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_civoid snd_seq_autoload_exit(void) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci atomic_inc(&snd_seq_in_init); 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_seq_autoload_exit); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_civoid snd_seq_device_load_drivers(void) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci queue_autoload_drivers(); 1338c2ecf20Sopenharmony_ci flush_work(&autoload_work); 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_seq_device_load_drivers); 1368c2ecf20Sopenharmony_ci#define cancel_autoload_drivers() cancel_work_sync(&autoload_work) 1378c2ecf20Sopenharmony_ci#else 1388c2ecf20Sopenharmony_ci#define queue_autoload_drivers() /* NOP */ 1398c2ecf20Sopenharmony_ci#define cancel_autoload_drivers() /* NOP */ 1408c2ecf20Sopenharmony_ci#endif 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci/* 1438c2ecf20Sopenharmony_ci * device management 1448c2ecf20Sopenharmony_ci */ 1458c2ecf20Sopenharmony_cistatic int snd_seq_device_dev_free(struct snd_device *device) 1468c2ecf20Sopenharmony_ci{ 1478c2ecf20Sopenharmony_ci struct snd_seq_device *dev = device->device_data; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci cancel_autoload_drivers(); 1508c2ecf20Sopenharmony_ci if (dev->private_free) 1518c2ecf20Sopenharmony_ci dev->private_free(dev); 1528c2ecf20Sopenharmony_ci put_device(&dev->dev); 1538c2ecf20Sopenharmony_ci return 0; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic int snd_seq_device_dev_register(struct snd_device *device) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci struct snd_seq_device *dev = device->device_data; 1598c2ecf20Sopenharmony_ci int err; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci err = device_add(&dev->dev); 1628c2ecf20Sopenharmony_ci if (err < 0) 1638c2ecf20Sopenharmony_ci return err; 1648c2ecf20Sopenharmony_ci if (!dev->dev.driver) 1658c2ecf20Sopenharmony_ci queue_autoload_drivers(); 1668c2ecf20Sopenharmony_ci return 0; 1678c2ecf20Sopenharmony_ci} 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_cistatic int snd_seq_device_dev_disconnect(struct snd_device *device) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci struct snd_seq_device *dev = device->device_data; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci device_del(&dev->dev); 1748c2ecf20Sopenharmony_ci return 0; 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistatic void snd_seq_dev_release(struct device *dev) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci kfree(to_seq_dev(dev)); 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci/* 1838c2ecf20Sopenharmony_ci * register a sequencer device 1848c2ecf20Sopenharmony_ci * card = card info 1858c2ecf20Sopenharmony_ci * device = device number (if any) 1868c2ecf20Sopenharmony_ci * id = id of driver 1878c2ecf20Sopenharmony_ci * result = return pointer (NULL allowed if unnecessary) 1888c2ecf20Sopenharmony_ci */ 1898c2ecf20Sopenharmony_ciint snd_seq_device_new(struct snd_card *card, int device, const char *id, 1908c2ecf20Sopenharmony_ci int argsize, struct snd_seq_device **result) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci struct snd_seq_device *dev; 1938c2ecf20Sopenharmony_ci int err; 1948c2ecf20Sopenharmony_ci static const struct snd_device_ops dops = { 1958c2ecf20Sopenharmony_ci .dev_free = snd_seq_device_dev_free, 1968c2ecf20Sopenharmony_ci .dev_register = snd_seq_device_dev_register, 1978c2ecf20Sopenharmony_ci .dev_disconnect = snd_seq_device_dev_disconnect, 1988c2ecf20Sopenharmony_ci }; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci if (result) 2018c2ecf20Sopenharmony_ci *result = NULL; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci if (snd_BUG_ON(!id)) 2048c2ecf20Sopenharmony_ci return -EINVAL; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci dev = kzalloc(sizeof(*dev) + argsize, GFP_KERNEL); 2078c2ecf20Sopenharmony_ci if (!dev) 2088c2ecf20Sopenharmony_ci return -ENOMEM; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci /* set up device info */ 2118c2ecf20Sopenharmony_ci dev->card = card; 2128c2ecf20Sopenharmony_ci dev->device = device; 2138c2ecf20Sopenharmony_ci dev->id = id; 2148c2ecf20Sopenharmony_ci dev->argsize = argsize; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci device_initialize(&dev->dev); 2178c2ecf20Sopenharmony_ci dev->dev.parent = &card->card_dev; 2188c2ecf20Sopenharmony_ci dev->dev.bus = &snd_seq_bus_type; 2198c2ecf20Sopenharmony_ci dev->dev.release = snd_seq_dev_release; 2208c2ecf20Sopenharmony_ci dev_set_name(&dev->dev, "%s-%d-%d", dev->id, card->number, device); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci /* add this device to the list */ 2238c2ecf20Sopenharmony_ci err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops); 2248c2ecf20Sopenharmony_ci if (err < 0) { 2258c2ecf20Sopenharmony_ci put_device(&dev->dev); 2268c2ecf20Sopenharmony_ci return err; 2278c2ecf20Sopenharmony_ci } 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci if (result) 2308c2ecf20Sopenharmony_ci *result = dev; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci return 0; 2338c2ecf20Sopenharmony_ci} 2348c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_seq_device_new); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci/* 2378c2ecf20Sopenharmony_ci * driver registration 2388c2ecf20Sopenharmony_ci */ 2398c2ecf20Sopenharmony_ciint __snd_seq_driver_register(struct snd_seq_driver *drv, struct module *mod) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci if (WARN_ON(!drv->driver.name || !drv->id)) 2428c2ecf20Sopenharmony_ci return -EINVAL; 2438c2ecf20Sopenharmony_ci drv->driver.bus = &snd_seq_bus_type; 2448c2ecf20Sopenharmony_ci drv->driver.owner = mod; 2458c2ecf20Sopenharmony_ci return driver_register(&drv->driver); 2468c2ecf20Sopenharmony_ci} 2478c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(__snd_seq_driver_register); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_civoid snd_seq_driver_unregister(struct snd_seq_driver *drv) 2508c2ecf20Sopenharmony_ci{ 2518c2ecf20Sopenharmony_ci driver_unregister(&drv->driver); 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_seq_driver_unregister); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci/* 2568c2ecf20Sopenharmony_ci * module part 2578c2ecf20Sopenharmony_ci */ 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_cistatic int __init seq_dev_proc_init(void) 2608c2ecf20Sopenharmony_ci{ 2618c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_PROC_FS 2628c2ecf20Sopenharmony_ci info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", 2638c2ecf20Sopenharmony_ci snd_seq_root); 2648c2ecf20Sopenharmony_ci if (info_entry == NULL) 2658c2ecf20Sopenharmony_ci return -ENOMEM; 2668c2ecf20Sopenharmony_ci info_entry->content = SNDRV_INFO_CONTENT_TEXT; 2678c2ecf20Sopenharmony_ci info_entry->c.text.read = snd_seq_device_info; 2688c2ecf20Sopenharmony_ci if (snd_info_register(info_entry) < 0) { 2698c2ecf20Sopenharmony_ci snd_info_free_entry(info_entry); 2708c2ecf20Sopenharmony_ci return -ENOMEM; 2718c2ecf20Sopenharmony_ci } 2728c2ecf20Sopenharmony_ci#endif 2738c2ecf20Sopenharmony_ci return 0; 2748c2ecf20Sopenharmony_ci} 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_cistatic int __init alsa_seq_device_init(void) 2778c2ecf20Sopenharmony_ci{ 2788c2ecf20Sopenharmony_ci int err; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci err = bus_register(&snd_seq_bus_type); 2818c2ecf20Sopenharmony_ci if (err < 0) 2828c2ecf20Sopenharmony_ci return err; 2838c2ecf20Sopenharmony_ci err = seq_dev_proc_init(); 2848c2ecf20Sopenharmony_ci if (err < 0) 2858c2ecf20Sopenharmony_ci bus_unregister(&snd_seq_bus_type); 2868c2ecf20Sopenharmony_ci return err; 2878c2ecf20Sopenharmony_ci} 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cistatic void __exit alsa_seq_device_exit(void) 2908c2ecf20Sopenharmony_ci{ 2918c2ecf20Sopenharmony_ci#ifdef CONFIG_MODULES 2928c2ecf20Sopenharmony_ci cancel_work_sync(&autoload_work); 2938c2ecf20Sopenharmony_ci#endif 2948c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_PROC_FS 2958c2ecf20Sopenharmony_ci snd_info_free_entry(info_entry); 2968c2ecf20Sopenharmony_ci#endif 2978c2ecf20Sopenharmony_ci bus_unregister(&snd_seq_bus_type); 2988c2ecf20Sopenharmony_ci} 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_cisubsys_initcall(alsa_seq_device_init) 3018c2ecf20Sopenharmony_cimodule_exit(alsa_seq_device_exit) 302