1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * ALSA sequencer device management |
4 | * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de> |
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 <tiwai@suse.de>" ); |
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, struct device_driver *drv) |
44 | { |
45 | struct snd_seq_device *sdev = to_seq_dev(dev); |
46 | 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 struct bus_type snd_seq_bus_type = { |
53 | .name = "snd_seq" , |
54 | .match = snd_seq_bus_match, |
55 | }; |
56 | |
57 | /* |
58 | * proc interface -- just for compatibility |
59 | */ |
60 | #ifdef CONFIG_SND_PROC_FS |
61 | static struct snd_info_entry *info_entry; |
62 | |
63 | static int print_dev_info(struct device *dev, void *data) |
64 | { |
65 | struct snd_seq_device *sdev = to_seq_dev(dev); |
66 | struct snd_info_buffer *buffer = data; |
67 | |
68 | snd_iprintf(buffer, "snd-%s,%s,%d\n" , sdev->id, |
69 | dev->driver ? "loaded" : "empty" , |
70 | dev->driver ? 1 : 0); |
71 | return 0; |
72 | } |
73 | |
74 | static void snd_seq_device_info(struct snd_info_entry *entry, |
75 | struct snd_info_buffer *buffer) |
76 | { |
77 | bus_for_each_dev(bus: &snd_seq_bus_type, NULL, data: buffer, fn: print_dev_info); |
78 | } |
79 | #endif |
80 | |
81 | /* |
82 | * load all registered drivers (called from seq_clientmgr.c) |
83 | */ |
84 | |
85 | #ifdef CONFIG_MODULES |
86 | /* flag to block auto-loading */ |
87 | static atomic_t snd_seq_in_init = ATOMIC_INIT(1); /* blocked as default */ |
88 | |
89 | static int request_seq_drv(struct device *dev, void *data) |
90 | { |
91 | struct snd_seq_device *sdev = to_seq_dev(dev); |
92 | |
93 | if (!dev->driver) |
94 | request_module("snd-%s" , sdev->id); |
95 | return 0; |
96 | } |
97 | |
98 | static void autoload_drivers(struct work_struct *work) |
99 | { |
100 | /* avoid reentrance */ |
101 | if (atomic_inc_return(v: &snd_seq_in_init) == 1) |
102 | bus_for_each_dev(bus: &snd_seq_bus_type, NULL, NULL, |
103 | fn: request_seq_drv); |
104 | atomic_dec(v: &snd_seq_in_init); |
105 | } |
106 | |
107 | static DECLARE_WORK(autoload_work, autoload_drivers); |
108 | |
109 | static void queue_autoload_drivers(void) |
110 | { |
111 | schedule_work(work: &autoload_work); |
112 | } |
113 | |
114 | void snd_seq_autoload_init(void) |
115 | { |
116 | atomic_dec(v: &snd_seq_in_init); |
117 | #ifdef CONFIG_SND_SEQUENCER_MODULE |
118 | /* initial autoload only when snd-seq is a module */ |
119 | queue_autoload_drivers(); |
120 | #endif |
121 | } |
122 | EXPORT_SYMBOL(snd_seq_autoload_init); |
123 | |
124 | void snd_seq_autoload_exit(void) |
125 | { |
126 | atomic_inc(v: &snd_seq_in_init); |
127 | } |
128 | EXPORT_SYMBOL(snd_seq_autoload_exit); |
129 | |
130 | void snd_seq_device_load_drivers(void) |
131 | { |
132 | queue_autoload_drivers(); |
133 | flush_work(work: &autoload_work); |
134 | } |
135 | EXPORT_SYMBOL(snd_seq_device_load_drivers); |
136 | |
137 | static inline void cancel_autoload_drivers(void) |
138 | { |
139 | cancel_work_sync(work: &autoload_work); |
140 | } |
141 | #else |
142 | static inline void queue_autoload_drivers(void) |
143 | { |
144 | } |
145 | |
146 | static inline void cancel_autoload_drivers(void) |
147 | { |
148 | } |
149 | #endif |
150 | |
151 | /* |
152 | * device management |
153 | */ |
154 | static int snd_seq_device_dev_free(struct snd_device *device) |
155 | { |
156 | struct snd_seq_device *dev = device->device_data; |
157 | |
158 | cancel_autoload_drivers(); |
159 | if (dev->private_free) |
160 | dev->private_free(dev); |
161 | put_device(dev: &dev->dev); |
162 | return 0; |
163 | } |
164 | |
165 | static int snd_seq_device_dev_register(struct snd_device *device) |
166 | { |
167 | struct snd_seq_device *dev = device->device_data; |
168 | int err; |
169 | |
170 | err = device_add(dev: &dev->dev); |
171 | if (err < 0) |
172 | return err; |
173 | if (!dev->dev.driver) |
174 | queue_autoload_drivers(); |
175 | return 0; |
176 | } |
177 | |
178 | static int snd_seq_device_dev_disconnect(struct snd_device *device) |
179 | { |
180 | struct snd_seq_device *dev = device->device_data; |
181 | |
182 | device_del(dev: &dev->dev); |
183 | return 0; |
184 | } |
185 | |
186 | static void snd_seq_dev_release(struct device *dev) |
187 | { |
188 | kfree(to_seq_dev(dev)); |
189 | } |
190 | |
191 | /* |
192 | * register a sequencer device |
193 | * card = card info |
194 | * device = device number (if any) |
195 | * id = id of driver |
196 | * result = return pointer (NULL allowed if unnecessary) |
197 | */ |
198 | int snd_seq_device_new(struct snd_card *card, int device, const char *id, |
199 | int argsize, struct snd_seq_device **result) |
200 | { |
201 | struct snd_seq_device *dev; |
202 | int err; |
203 | static const struct snd_device_ops dops = { |
204 | .dev_free = snd_seq_device_dev_free, |
205 | .dev_register = snd_seq_device_dev_register, |
206 | .dev_disconnect = snd_seq_device_dev_disconnect, |
207 | }; |
208 | |
209 | if (result) |
210 | *result = NULL; |
211 | |
212 | if (snd_BUG_ON(!id)) |
213 | return -EINVAL; |
214 | |
215 | dev = kzalloc(size: sizeof(*dev) + argsize, GFP_KERNEL); |
216 | if (!dev) |
217 | return -ENOMEM; |
218 | |
219 | /* set up device info */ |
220 | dev->card = card; |
221 | dev->device = device; |
222 | dev->id = id; |
223 | dev->argsize = argsize; |
224 | |
225 | device_initialize(dev: &dev->dev); |
226 | dev->dev.parent = &card->card_dev; |
227 | dev->dev.bus = &snd_seq_bus_type; |
228 | dev->dev.release = snd_seq_dev_release; |
229 | dev_set_name(dev: &dev->dev, name: "%s-%d-%d" , dev->id, card->number, device); |
230 | |
231 | /* add this device to the list */ |
232 | err = snd_device_new(card, type: SNDRV_DEV_SEQUENCER, device_data: dev, ops: &dops); |
233 | if (err < 0) { |
234 | put_device(dev: &dev->dev); |
235 | return err; |
236 | } |
237 | |
238 | if (result) |
239 | *result = dev; |
240 | |
241 | return 0; |
242 | } |
243 | EXPORT_SYMBOL(snd_seq_device_new); |
244 | |
245 | /* |
246 | * driver registration |
247 | */ |
248 | int __snd_seq_driver_register(struct snd_seq_driver *drv, struct module *mod) |
249 | { |
250 | if (WARN_ON(!drv->driver.name || !drv->id)) |
251 | return -EINVAL; |
252 | drv->driver.bus = &snd_seq_bus_type; |
253 | drv->driver.owner = mod; |
254 | return driver_register(drv: &drv->driver); |
255 | } |
256 | EXPORT_SYMBOL_GPL(__snd_seq_driver_register); |
257 | |
258 | void snd_seq_driver_unregister(struct snd_seq_driver *drv) |
259 | { |
260 | driver_unregister(drv: &drv->driver); |
261 | } |
262 | EXPORT_SYMBOL_GPL(snd_seq_driver_unregister); |
263 | |
264 | /* |
265 | * module part |
266 | */ |
267 | |
268 | static int __init seq_dev_proc_init(void) |
269 | { |
270 | #ifdef CONFIG_SND_PROC_FS |
271 | info_entry = snd_info_create_module_entry(THIS_MODULE, name: "drivers" , |
272 | parent: snd_seq_root); |
273 | if (info_entry == NULL) |
274 | return -ENOMEM; |
275 | info_entry->content = SNDRV_INFO_CONTENT_TEXT; |
276 | info_entry->c.text.read = snd_seq_device_info; |
277 | if (snd_info_register(entry: info_entry) < 0) { |
278 | snd_info_free_entry(entry: info_entry); |
279 | return -ENOMEM; |
280 | } |
281 | #endif |
282 | return 0; |
283 | } |
284 | |
285 | static int __init alsa_seq_device_init(void) |
286 | { |
287 | int err; |
288 | |
289 | err = bus_register(bus: &snd_seq_bus_type); |
290 | if (err < 0) |
291 | return err; |
292 | err = seq_dev_proc_init(); |
293 | if (err < 0) |
294 | bus_unregister(bus: &snd_seq_bus_type); |
295 | return err; |
296 | } |
297 | |
298 | static void __exit alsa_seq_device_exit(void) |
299 | { |
300 | #ifdef CONFIG_MODULES |
301 | cancel_work_sync(work: &autoload_work); |
302 | #endif |
303 | #ifdef CONFIG_SND_PROC_FS |
304 | snd_info_free_entry(entry: info_entry); |
305 | #endif |
306 | bus_unregister(bus: &snd_seq_bus_type); |
307 | } |
308 | |
309 | subsys_initcall(alsa_seq_device_init) |
310 | module_exit(alsa_seq_device_exit) |
311 | |