1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Hardware dependent layer |
4 | * Copyright (c) by Jaroslav Kysela <perex@perex.cz> |
5 | */ |
6 | |
7 | #include <linux/major.h> |
8 | #include <linux/init.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/time.h> |
11 | #include <linux/mutex.h> |
12 | #include <linux/module.h> |
13 | #include <linux/sched/signal.h> |
14 | #include <sound/core.h> |
15 | #include <sound/control.h> |
16 | #include <sound/minors.h> |
17 | #include <sound/hwdep.h> |
18 | #include <sound/info.h> |
19 | |
20 | MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>" ); |
21 | MODULE_DESCRIPTION("Hardware dependent layer" ); |
22 | MODULE_LICENSE("GPL" ); |
23 | |
24 | static LIST_HEAD(snd_hwdep_devices); |
25 | static DEFINE_MUTEX(register_mutex); |
26 | |
27 | static int snd_hwdep_dev_free(struct snd_device *device); |
28 | static int snd_hwdep_dev_register(struct snd_device *device); |
29 | static int snd_hwdep_dev_disconnect(struct snd_device *device); |
30 | |
31 | |
32 | static struct snd_hwdep *snd_hwdep_search(struct snd_card *card, int device) |
33 | { |
34 | struct snd_hwdep *hwdep; |
35 | |
36 | list_for_each_entry(hwdep, &snd_hwdep_devices, list) |
37 | if (hwdep->card == card && hwdep->device == device) |
38 | return hwdep; |
39 | return NULL; |
40 | } |
41 | |
42 | static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig) |
43 | { |
44 | struct snd_hwdep *hw = file->private_data; |
45 | if (hw->ops.llseek) |
46 | return hw->ops.llseek(hw, file, offset, orig); |
47 | return -ENXIO; |
48 | } |
49 | |
50 | static ssize_t snd_hwdep_read(struct file * file, char __user *buf, |
51 | size_t count, loff_t *offset) |
52 | { |
53 | struct snd_hwdep *hw = file->private_data; |
54 | if (hw->ops.read) |
55 | return hw->ops.read(hw, buf, count, offset); |
56 | return -ENXIO; |
57 | } |
58 | |
59 | static ssize_t snd_hwdep_write(struct file * file, const char __user *buf, |
60 | size_t count, loff_t *offset) |
61 | { |
62 | struct snd_hwdep *hw = file->private_data; |
63 | if (hw->ops.write) |
64 | return hw->ops.write(hw, buf, count, offset); |
65 | return -ENXIO; |
66 | } |
67 | |
68 | static int snd_hwdep_open(struct inode *inode, struct file * file) |
69 | { |
70 | int major = imajor(inode); |
71 | struct snd_hwdep *hw; |
72 | int err; |
73 | wait_queue_entry_t wait; |
74 | |
75 | if (major == snd_major) { |
76 | hw = snd_lookup_minor_data(minor: iminor(inode), |
77 | type: SNDRV_DEVICE_TYPE_HWDEP); |
78 | #ifdef CONFIG_SND_OSSEMUL |
79 | } else if (major == SOUND_MAJOR) { |
80 | hw = snd_lookup_oss_minor_data(minor: iminor(inode), |
81 | SNDRV_OSS_DEVICE_TYPE_DMFM); |
82 | #endif |
83 | } else |
84 | return -ENXIO; |
85 | if (hw == NULL) |
86 | return -ENODEV; |
87 | |
88 | if (!try_module_get(module: hw->card->module)) { |
89 | snd_card_unref(card: hw->card); |
90 | return -EFAULT; |
91 | } |
92 | |
93 | init_waitqueue_entry(wq_entry: &wait, current); |
94 | add_wait_queue(wq_head: &hw->open_wait, wq_entry: &wait); |
95 | mutex_lock(&hw->open_mutex); |
96 | while (1) { |
97 | if (hw->exclusive && hw->used > 0) { |
98 | err = -EBUSY; |
99 | break; |
100 | } |
101 | if (!hw->ops.open) { |
102 | err = 0; |
103 | break; |
104 | } |
105 | err = hw->ops.open(hw, file); |
106 | if (err >= 0) |
107 | break; |
108 | if (err == -EAGAIN) { |
109 | if (file->f_flags & O_NONBLOCK) { |
110 | err = -EBUSY; |
111 | break; |
112 | } |
113 | } else |
114 | break; |
115 | set_current_state(TASK_INTERRUPTIBLE); |
116 | mutex_unlock(lock: &hw->open_mutex); |
117 | schedule(); |
118 | mutex_lock(&hw->open_mutex); |
119 | if (hw->card->shutdown) { |
120 | err = -ENODEV; |
121 | break; |
122 | } |
123 | if (signal_pending(current)) { |
124 | err = -ERESTARTSYS; |
125 | break; |
126 | } |
127 | } |
128 | remove_wait_queue(wq_head: &hw->open_wait, wq_entry: &wait); |
129 | if (err >= 0) { |
130 | err = snd_card_file_add(card: hw->card, file); |
131 | if (err >= 0) { |
132 | file->private_data = hw; |
133 | hw->used++; |
134 | } else { |
135 | if (hw->ops.release) |
136 | hw->ops.release(hw, file); |
137 | } |
138 | } |
139 | mutex_unlock(lock: &hw->open_mutex); |
140 | if (err < 0) |
141 | module_put(module: hw->card->module); |
142 | snd_card_unref(card: hw->card); |
143 | return err; |
144 | } |
145 | |
146 | static int snd_hwdep_release(struct inode *inode, struct file * file) |
147 | { |
148 | int err = 0; |
149 | struct snd_hwdep *hw = file->private_data; |
150 | struct module *mod = hw->card->module; |
151 | |
152 | mutex_lock(&hw->open_mutex); |
153 | if (hw->ops.release) |
154 | err = hw->ops.release(hw, file); |
155 | if (hw->used > 0) |
156 | hw->used--; |
157 | mutex_unlock(lock: &hw->open_mutex); |
158 | wake_up(&hw->open_wait); |
159 | |
160 | snd_card_file_remove(card: hw->card, file); |
161 | module_put(module: mod); |
162 | return err; |
163 | } |
164 | |
165 | static __poll_t snd_hwdep_poll(struct file * file, poll_table * wait) |
166 | { |
167 | struct snd_hwdep *hw = file->private_data; |
168 | if (hw->ops.poll) |
169 | return hw->ops.poll(hw, file, wait); |
170 | return 0; |
171 | } |
172 | |
173 | static int snd_hwdep_info(struct snd_hwdep *hw, |
174 | struct snd_hwdep_info __user *_info) |
175 | { |
176 | struct snd_hwdep_info info; |
177 | |
178 | memset(&info, 0, sizeof(info)); |
179 | info.card = hw->card->number; |
180 | strscpy(p: info.id, q: hw->id, size: sizeof(info.id)); |
181 | strscpy(p: info.name, q: hw->name, size: sizeof(info.name)); |
182 | info.iface = hw->iface; |
183 | if (copy_to_user(to: _info, from: &info, n: sizeof(info))) |
184 | return -EFAULT; |
185 | return 0; |
186 | } |
187 | |
188 | static int snd_hwdep_dsp_status(struct snd_hwdep *hw, |
189 | struct snd_hwdep_dsp_status __user *_info) |
190 | { |
191 | struct snd_hwdep_dsp_status info; |
192 | int err; |
193 | |
194 | if (! hw->ops.dsp_status) |
195 | return -ENXIO; |
196 | memset(&info, 0, sizeof(info)); |
197 | info.dsp_loaded = hw->dsp_loaded; |
198 | err = hw->ops.dsp_status(hw, &info); |
199 | if (err < 0) |
200 | return err; |
201 | if (copy_to_user(to: _info, from: &info, n: sizeof(info))) |
202 | return -EFAULT; |
203 | return 0; |
204 | } |
205 | |
206 | static int snd_hwdep_dsp_load(struct snd_hwdep *hw, |
207 | struct snd_hwdep_dsp_image *info) |
208 | { |
209 | int err; |
210 | |
211 | if (! hw->ops.dsp_load) |
212 | return -ENXIO; |
213 | if (info->index >= 32) |
214 | return -EINVAL; |
215 | /* check whether the dsp was already loaded */ |
216 | if (hw->dsp_loaded & (1u << info->index)) |
217 | return -EBUSY; |
218 | err = hw->ops.dsp_load(hw, info); |
219 | if (err < 0) |
220 | return err; |
221 | hw->dsp_loaded |= (1u << info->index); |
222 | return 0; |
223 | } |
224 | |
225 | static int snd_hwdep_dsp_load_user(struct snd_hwdep *hw, |
226 | struct snd_hwdep_dsp_image __user *_info) |
227 | { |
228 | struct snd_hwdep_dsp_image info = {}; |
229 | |
230 | if (copy_from_user(to: &info, from: _info, n: sizeof(info))) |
231 | return -EFAULT; |
232 | return snd_hwdep_dsp_load(hw, info: &info); |
233 | } |
234 | |
235 | |
236 | static long snd_hwdep_ioctl(struct file * file, unsigned int cmd, |
237 | unsigned long arg) |
238 | { |
239 | struct snd_hwdep *hw = file->private_data; |
240 | void __user *argp = (void __user *)arg; |
241 | switch (cmd) { |
242 | case SNDRV_HWDEP_IOCTL_PVERSION: |
243 | return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp); |
244 | case SNDRV_HWDEP_IOCTL_INFO: |
245 | return snd_hwdep_info(hw, info: argp); |
246 | case SNDRV_HWDEP_IOCTL_DSP_STATUS: |
247 | return snd_hwdep_dsp_status(hw, info: argp); |
248 | case SNDRV_HWDEP_IOCTL_DSP_LOAD: |
249 | return snd_hwdep_dsp_load_user(hw, info: argp); |
250 | } |
251 | if (hw->ops.ioctl) |
252 | return hw->ops.ioctl(hw, file, cmd, arg); |
253 | return -ENOTTY; |
254 | } |
255 | |
256 | static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma) |
257 | { |
258 | struct snd_hwdep *hw = file->private_data; |
259 | if (hw->ops.mmap) |
260 | return hw->ops.mmap(hw, file, vma); |
261 | return -ENXIO; |
262 | } |
263 | |
264 | static int snd_hwdep_control_ioctl(struct snd_card *card, |
265 | struct snd_ctl_file * control, |
266 | unsigned int cmd, unsigned long arg) |
267 | { |
268 | switch (cmd) { |
269 | case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE: |
270 | { |
271 | int device; |
272 | |
273 | if (get_user(device, (int __user *)arg)) |
274 | return -EFAULT; |
275 | mutex_lock(®ister_mutex); |
276 | |
277 | if (device < 0) |
278 | device = 0; |
279 | else if (device < SNDRV_MINOR_HWDEPS) |
280 | device++; |
281 | else |
282 | device = SNDRV_MINOR_HWDEPS; |
283 | |
284 | while (device < SNDRV_MINOR_HWDEPS) { |
285 | if (snd_hwdep_search(card, device)) |
286 | break; |
287 | device++; |
288 | } |
289 | if (device >= SNDRV_MINOR_HWDEPS) |
290 | device = -1; |
291 | mutex_unlock(lock: ®ister_mutex); |
292 | if (put_user(device, (int __user *)arg)) |
293 | return -EFAULT; |
294 | return 0; |
295 | } |
296 | case SNDRV_CTL_IOCTL_HWDEP_INFO: |
297 | { |
298 | struct snd_hwdep_info __user *info = (struct snd_hwdep_info __user *)arg; |
299 | int device, err; |
300 | struct snd_hwdep *hwdep; |
301 | |
302 | if (get_user(device, &info->device)) |
303 | return -EFAULT; |
304 | mutex_lock(®ister_mutex); |
305 | hwdep = snd_hwdep_search(card, device); |
306 | if (hwdep) |
307 | err = snd_hwdep_info(hw: hwdep, info: info); |
308 | else |
309 | err = -ENXIO; |
310 | mutex_unlock(lock: ®ister_mutex); |
311 | return err; |
312 | } |
313 | } |
314 | return -ENOIOCTLCMD; |
315 | } |
316 | |
317 | #ifdef CONFIG_COMPAT |
318 | #include "hwdep_compat.c" |
319 | #else |
320 | #define snd_hwdep_ioctl_compat NULL |
321 | #endif |
322 | |
323 | /* |
324 | |
325 | */ |
326 | |
327 | static const struct file_operations snd_hwdep_f_ops = |
328 | { |
329 | .owner = THIS_MODULE, |
330 | .llseek = snd_hwdep_llseek, |
331 | .read = snd_hwdep_read, |
332 | .write = snd_hwdep_write, |
333 | .open = snd_hwdep_open, |
334 | .release = snd_hwdep_release, |
335 | .poll = snd_hwdep_poll, |
336 | .unlocked_ioctl = snd_hwdep_ioctl, |
337 | .compat_ioctl = snd_hwdep_ioctl_compat, |
338 | .mmap = snd_hwdep_mmap, |
339 | }; |
340 | |
341 | static void snd_hwdep_free(struct snd_hwdep *hwdep) |
342 | { |
343 | if (!hwdep) |
344 | return; |
345 | if (hwdep->private_free) |
346 | hwdep->private_free(hwdep); |
347 | put_device(dev: hwdep->dev); |
348 | kfree(objp: hwdep); |
349 | } |
350 | |
351 | /** |
352 | * snd_hwdep_new - create a new hwdep instance |
353 | * @card: the card instance |
354 | * @id: the id string |
355 | * @device: the device index (zero-based) |
356 | * @rhwdep: the pointer to store the new hwdep instance |
357 | * |
358 | * Creates a new hwdep instance with the given index on the card. |
359 | * The callbacks (hwdep->ops) must be set on the returned instance |
360 | * after this call manually by the caller. |
361 | * |
362 | * Return: Zero if successful, or a negative error code on failure. |
363 | */ |
364 | int snd_hwdep_new(struct snd_card *card, char *id, int device, |
365 | struct snd_hwdep **rhwdep) |
366 | { |
367 | struct snd_hwdep *hwdep; |
368 | int err; |
369 | static const struct snd_device_ops ops = { |
370 | .dev_free = snd_hwdep_dev_free, |
371 | .dev_register = snd_hwdep_dev_register, |
372 | .dev_disconnect = snd_hwdep_dev_disconnect, |
373 | }; |
374 | |
375 | if (snd_BUG_ON(!card)) |
376 | return -ENXIO; |
377 | if (rhwdep) |
378 | *rhwdep = NULL; |
379 | hwdep = kzalloc(size: sizeof(*hwdep), GFP_KERNEL); |
380 | if (!hwdep) |
381 | return -ENOMEM; |
382 | |
383 | init_waitqueue_head(&hwdep->open_wait); |
384 | mutex_init(&hwdep->open_mutex); |
385 | hwdep->card = card; |
386 | hwdep->device = device; |
387 | if (id) |
388 | strscpy(p: hwdep->id, q: id, size: sizeof(hwdep->id)); |
389 | |
390 | err = snd_device_alloc(dev_p: &hwdep->dev, card); |
391 | if (err < 0) { |
392 | snd_hwdep_free(hwdep); |
393 | return err; |
394 | } |
395 | |
396 | dev_set_name(dev: hwdep->dev, name: "hwC%iD%i" , card->number, device); |
397 | #ifdef CONFIG_SND_OSSEMUL |
398 | hwdep->oss_type = -1; |
399 | #endif |
400 | |
401 | err = snd_device_new(card, type: SNDRV_DEV_HWDEP, device_data: hwdep, ops: &ops); |
402 | if (err < 0) { |
403 | snd_hwdep_free(hwdep); |
404 | return err; |
405 | } |
406 | |
407 | if (rhwdep) |
408 | *rhwdep = hwdep; |
409 | return 0; |
410 | } |
411 | EXPORT_SYMBOL(snd_hwdep_new); |
412 | |
413 | static int snd_hwdep_dev_free(struct snd_device *device) |
414 | { |
415 | snd_hwdep_free(hwdep: device->device_data); |
416 | return 0; |
417 | } |
418 | |
419 | static int snd_hwdep_dev_register(struct snd_device *device) |
420 | { |
421 | struct snd_hwdep *hwdep = device->device_data; |
422 | struct snd_card *card = hwdep->card; |
423 | int err; |
424 | |
425 | mutex_lock(®ister_mutex); |
426 | if (snd_hwdep_search(card, device: hwdep->device)) { |
427 | mutex_unlock(lock: ®ister_mutex); |
428 | return -EBUSY; |
429 | } |
430 | list_add_tail(new: &hwdep->list, head: &snd_hwdep_devices); |
431 | err = snd_register_device(type: SNDRV_DEVICE_TYPE_HWDEP, |
432 | card: hwdep->card, dev: hwdep->device, |
433 | f_ops: &snd_hwdep_f_ops, private_data: hwdep, device: hwdep->dev); |
434 | if (err < 0) { |
435 | dev_err(hwdep->dev, "unable to register\n" ); |
436 | list_del(entry: &hwdep->list); |
437 | mutex_unlock(lock: ®ister_mutex); |
438 | return err; |
439 | } |
440 | |
441 | #ifdef CONFIG_SND_OSSEMUL |
442 | hwdep->ossreg = 0; |
443 | if (hwdep->oss_type >= 0) { |
444 | if (hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM && |
445 | hwdep->device) |
446 | dev_warn(hwdep->dev, |
447 | "only hwdep device 0 can be registered as OSS direct FM device!\n" ); |
448 | else if (snd_register_oss_device(type: hwdep->oss_type, |
449 | card, dev: hwdep->device, |
450 | f_ops: &snd_hwdep_f_ops, private_data: hwdep) < 0) |
451 | dev_warn(hwdep->dev, |
452 | "unable to register OSS compatibility device\n" ); |
453 | else |
454 | hwdep->ossreg = 1; |
455 | } |
456 | #endif |
457 | mutex_unlock(lock: ®ister_mutex); |
458 | return 0; |
459 | } |
460 | |
461 | static int snd_hwdep_dev_disconnect(struct snd_device *device) |
462 | { |
463 | struct snd_hwdep *hwdep = device->device_data; |
464 | |
465 | if (snd_BUG_ON(!hwdep)) |
466 | return -ENXIO; |
467 | mutex_lock(®ister_mutex); |
468 | if (snd_hwdep_search(card: hwdep->card, device: hwdep->device) != hwdep) { |
469 | mutex_unlock(lock: ®ister_mutex); |
470 | return -EINVAL; |
471 | } |
472 | mutex_lock(&hwdep->open_mutex); |
473 | wake_up(&hwdep->open_wait); |
474 | #ifdef CONFIG_SND_OSSEMUL |
475 | if (hwdep->ossreg) |
476 | snd_unregister_oss_device(type: hwdep->oss_type, card: hwdep->card, dev: hwdep->device); |
477 | #endif |
478 | snd_unregister_device(dev: hwdep->dev); |
479 | list_del_init(entry: &hwdep->list); |
480 | mutex_unlock(lock: &hwdep->open_mutex); |
481 | mutex_unlock(lock: ®ister_mutex); |
482 | return 0; |
483 | } |
484 | |
485 | #ifdef CONFIG_SND_PROC_FS |
486 | /* |
487 | * Info interface |
488 | */ |
489 | |
490 | static void snd_hwdep_proc_read(struct snd_info_entry *entry, |
491 | struct snd_info_buffer *buffer) |
492 | { |
493 | struct snd_hwdep *hwdep; |
494 | |
495 | mutex_lock(®ister_mutex); |
496 | list_for_each_entry(hwdep, &snd_hwdep_devices, list) |
497 | snd_iprintf(buffer, "%02i-%02i: %s\n" , |
498 | hwdep->card->number, hwdep->device, hwdep->name); |
499 | mutex_unlock(lock: ®ister_mutex); |
500 | } |
501 | |
502 | static struct snd_info_entry *snd_hwdep_proc_entry; |
503 | |
504 | static void __init snd_hwdep_proc_init(void) |
505 | { |
506 | struct snd_info_entry *entry; |
507 | |
508 | entry = snd_info_create_module_entry(THIS_MODULE, name: "hwdep" , NULL); |
509 | if (entry) { |
510 | entry->c.text.read = snd_hwdep_proc_read; |
511 | if (snd_info_register(entry) < 0) { |
512 | snd_info_free_entry(entry); |
513 | entry = NULL; |
514 | } |
515 | } |
516 | snd_hwdep_proc_entry = entry; |
517 | } |
518 | |
519 | static void __exit snd_hwdep_proc_done(void) |
520 | { |
521 | snd_info_free_entry(entry: snd_hwdep_proc_entry); |
522 | } |
523 | #else /* !CONFIG_SND_PROC_FS */ |
524 | #define snd_hwdep_proc_init() |
525 | #define snd_hwdep_proc_done() |
526 | #endif /* CONFIG_SND_PROC_FS */ |
527 | |
528 | |
529 | /* |
530 | * ENTRY functions |
531 | */ |
532 | |
533 | static int __init alsa_hwdep_init(void) |
534 | { |
535 | snd_hwdep_proc_init(); |
536 | snd_ctl_register_ioctl(fcn: snd_hwdep_control_ioctl); |
537 | snd_ctl_register_ioctl_compat(fcn: snd_hwdep_control_ioctl); |
538 | return 0; |
539 | } |
540 | |
541 | static void __exit alsa_hwdep_exit(void) |
542 | { |
543 | snd_ctl_unregister_ioctl(fcn: snd_hwdep_control_ioctl); |
544 | snd_ctl_unregister_ioctl_compat(fcn: snd_hwdep_control_ioctl); |
545 | snd_hwdep_proc_done(); |
546 | } |
547 | |
548 | module_init(alsa_hwdep_init) |
549 | module_exit(alsa_hwdep_exit) |
550 | |