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 | scoped_guard(mutex, &hw->open_mutex) { |
153 | if (hw->ops.release) |
154 | err = hw->ops.release(hw, file); |
155 | if (hw->used > 0) |
156 | hw->used--; |
157 | } |
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(info.id, hw->id, sizeof(info.id)); |
181 | strscpy(info.name, hw->name, 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 | |
276 | scoped_guard(mutex, ®ister_mutex) { |
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 | } |
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; |
300 | struct snd_hwdep *hwdep; |
301 | |
302 | if (get_user(device, &info->device)) |
303 | return -EFAULT; |
304 | scoped_guard(mutex, ®ister_mutex) { |
305 | hwdep = snd_hwdep_search(card, device); |
306 | if (!hwdep) |
307 | return -ENXIO; |
308 | return snd_hwdep_info(hw: hwdep, info: info); |
309 | } |
310 | break; |
311 | } |
312 | } |
313 | return -ENOIOCTLCMD; |
314 | } |
315 | |
316 | #ifdef CONFIG_COMPAT |
317 | #include "hwdep_compat.c" |
318 | #else |
319 | #define snd_hwdep_ioctl_compat NULL |
320 | #endif |
321 | |
322 | /* |
323 | |
324 | */ |
325 | |
326 | static const struct file_operations snd_hwdep_f_ops = |
327 | { |
328 | .owner = THIS_MODULE, |
329 | .llseek = snd_hwdep_llseek, |
330 | .read = snd_hwdep_read, |
331 | .write = snd_hwdep_write, |
332 | .open = snd_hwdep_open, |
333 | .release = snd_hwdep_release, |
334 | .poll = snd_hwdep_poll, |
335 | .unlocked_ioctl = snd_hwdep_ioctl, |
336 | .compat_ioctl = snd_hwdep_ioctl_compat, |
337 | .mmap = snd_hwdep_mmap, |
338 | }; |
339 | |
340 | static void snd_hwdep_free(struct snd_hwdep *hwdep) |
341 | { |
342 | if (!hwdep) |
343 | return; |
344 | if (hwdep->private_free) |
345 | hwdep->private_free(hwdep); |
346 | put_device(dev: hwdep->dev); |
347 | kfree(objp: hwdep); |
348 | } |
349 | |
350 | /** |
351 | * snd_hwdep_new - create a new hwdep instance |
352 | * @card: the card instance |
353 | * @id: the id string |
354 | * @device: the device index (zero-based) |
355 | * @rhwdep: the pointer to store the new hwdep instance |
356 | * |
357 | * Creates a new hwdep instance with the given index on the card. |
358 | * The callbacks (hwdep->ops) must be set on the returned instance |
359 | * after this call manually by the caller. |
360 | * |
361 | * Return: Zero if successful, or a negative error code on failure. |
362 | */ |
363 | int snd_hwdep_new(struct snd_card *card, char *id, int device, |
364 | struct snd_hwdep **rhwdep) |
365 | { |
366 | struct snd_hwdep *hwdep; |
367 | int err; |
368 | static const struct snd_device_ops ops = { |
369 | .dev_free = snd_hwdep_dev_free, |
370 | .dev_register = snd_hwdep_dev_register, |
371 | .dev_disconnect = snd_hwdep_dev_disconnect, |
372 | }; |
373 | |
374 | if (snd_BUG_ON(!card)) |
375 | return -ENXIO; |
376 | if (rhwdep) |
377 | *rhwdep = NULL; |
378 | hwdep = kzalloc(size: sizeof(*hwdep), GFP_KERNEL); |
379 | if (!hwdep) |
380 | return -ENOMEM; |
381 | |
382 | init_waitqueue_head(&hwdep->open_wait); |
383 | mutex_init(&hwdep->open_mutex); |
384 | hwdep->card = card; |
385 | hwdep->device = device; |
386 | if (id) |
387 | strscpy(hwdep->id, id, sizeof(hwdep->id)); |
388 | |
389 | err = snd_device_alloc(dev_p: &hwdep->dev, card); |
390 | if (err < 0) { |
391 | snd_hwdep_free(hwdep); |
392 | return err; |
393 | } |
394 | |
395 | dev_set_name(dev: hwdep->dev, name: "hwC%iD%i" , card->number, device); |
396 | #ifdef CONFIG_SND_OSSEMUL |
397 | hwdep->oss_type = -1; |
398 | #endif |
399 | |
400 | err = snd_device_new(card, type: SNDRV_DEV_HWDEP, device_data: hwdep, ops: &ops); |
401 | if (err < 0) { |
402 | snd_hwdep_free(hwdep); |
403 | return err; |
404 | } |
405 | |
406 | if (rhwdep) |
407 | *rhwdep = hwdep; |
408 | return 0; |
409 | } |
410 | EXPORT_SYMBOL(snd_hwdep_new); |
411 | |
412 | static int snd_hwdep_dev_free(struct snd_device *device) |
413 | { |
414 | snd_hwdep_free(hwdep: device->device_data); |
415 | return 0; |
416 | } |
417 | |
418 | static int snd_hwdep_dev_register(struct snd_device *device) |
419 | { |
420 | struct snd_hwdep *hwdep = device->device_data; |
421 | struct snd_card *card = hwdep->card; |
422 | int err; |
423 | |
424 | guard(mutex)(T: ®ister_mutex); |
425 | if (snd_hwdep_search(card, device: hwdep->device)) |
426 | return -EBUSY; |
427 | list_add_tail(new: &hwdep->list, head: &snd_hwdep_devices); |
428 | err = snd_register_device(type: SNDRV_DEVICE_TYPE_HWDEP, |
429 | card: hwdep->card, dev: hwdep->device, |
430 | f_ops: &snd_hwdep_f_ops, private_data: hwdep, device: hwdep->dev); |
431 | if (err < 0) { |
432 | dev_err(hwdep->dev, "unable to register\n" ); |
433 | list_del(entry: &hwdep->list); |
434 | return err; |
435 | } |
436 | |
437 | #ifdef CONFIG_SND_OSSEMUL |
438 | hwdep->ossreg = 0; |
439 | if (hwdep->oss_type >= 0) { |
440 | if (hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM && |
441 | hwdep->device) |
442 | dev_warn(hwdep->dev, |
443 | "only hwdep device 0 can be registered as OSS direct FM device!\n" ); |
444 | else if (snd_register_oss_device(type: hwdep->oss_type, |
445 | card, dev: hwdep->device, |
446 | f_ops: &snd_hwdep_f_ops, private_data: hwdep) < 0) |
447 | dev_warn(hwdep->dev, |
448 | "unable to register OSS compatibility device\n" ); |
449 | else |
450 | hwdep->ossreg = 1; |
451 | } |
452 | #endif |
453 | return 0; |
454 | } |
455 | |
456 | static int snd_hwdep_dev_disconnect(struct snd_device *device) |
457 | { |
458 | struct snd_hwdep *hwdep = device->device_data; |
459 | |
460 | if (snd_BUG_ON(!hwdep)) |
461 | return -ENXIO; |
462 | guard(mutex)(T: ®ister_mutex); |
463 | if (snd_hwdep_search(card: hwdep->card, device: hwdep->device) != hwdep) |
464 | return -EINVAL; |
465 | guard(mutex)(T: &hwdep->open_mutex); |
466 | wake_up(&hwdep->open_wait); |
467 | #ifdef CONFIG_SND_OSSEMUL |
468 | if (hwdep->ossreg) |
469 | snd_unregister_oss_device(type: hwdep->oss_type, card: hwdep->card, dev: hwdep->device); |
470 | #endif |
471 | snd_unregister_device(dev: hwdep->dev); |
472 | list_del_init(entry: &hwdep->list); |
473 | return 0; |
474 | } |
475 | |
476 | #ifdef CONFIG_SND_PROC_FS |
477 | /* |
478 | * Info interface |
479 | */ |
480 | |
481 | static void snd_hwdep_proc_read(struct snd_info_entry *entry, |
482 | struct snd_info_buffer *buffer) |
483 | { |
484 | struct snd_hwdep *hwdep; |
485 | |
486 | guard(mutex)(T: ®ister_mutex); |
487 | list_for_each_entry(hwdep, &snd_hwdep_devices, list) |
488 | snd_iprintf(buffer, "%02i-%02i: %s\n" , |
489 | hwdep->card->number, hwdep->device, hwdep->name); |
490 | } |
491 | |
492 | static struct snd_info_entry *snd_hwdep_proc_entry; |
493 | |
494 | static void __init snd_hwdep_proc_init(void) |
495 | { |
496 | struct snd_info_entry *entry; |
497 | |
498 | entry = snd_info_create_module_entry(THIS_MODULE, name: "hwdep" , NULL); |
499 | if (entry) { |
500 | entry->c.text.read = snd_hwdep_proc_read; |
501 | if (snd_info_register(entry) < 0) { |
502 | snd_info_free_entry(entry); |
503 | entry = NULL; |
504 | } |
505 | } |
506 | snd_hwdep_proc_entry = entry; |
507 | } |
508 | |
509 | static void __exit snd_hwdep_proc_done(void) |
510 | { |
511 | snd_info_free_entry(entry: snd_hwdep_proc_entry); |
512 | } |
513 | #else /* !CONFIG_SND_PROC_FS */ |
514 | #define snd_hwdep_proc_init() |
515 | #define snd_hwdep_proc_done() |
516 | #endif /* CONFIG_SND_PROC_FS */ |
517 | |
518 | |
519 | /* |
520 | * ENTRY functions |
521 | */ |
522 | |
523 | static int __init alsa_hwdep_init(void) |
524 | { |
525 | snd_hwdep_proc_init(); |
526 | snd_ctl_register_ioctl(fcn: snd_hwdep_control_ioctl); |
527 | snd_ctl_register_ioctl_compat(fcn: snd_hwdep_control_ioctl); |
528 | return 0; |
529 | } |
530 | |
531 | static void __exit alsa_hwdep_exit(void) |
532 | { |
533 | snd_ctl_unregister_ioctl(fcn: snd_hwdep_control_ioctl); |
534 | snd_ctl_unregister_ioctl_compat(fcn: snd_hwdep_control_ioctl); |
535 | snd_hwdep_proc_done(); |
536 | } |
537 | |
538 | module_init(alsa_hwdep_init) |
539 | module_exit(alsa_hwdep_exit) |
540 | |