540 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Hardware dependent layer4 * 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 20MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");21MODULE_DESCRIPTION("Hardware dependent layer");22MODULE_LICENSE("GPL");23 24static LIST_HEAD(snd_hwdep_devices);25static DEFINE_MUTEX(register_mutex);26 27static int snd_hwdep_dev_free(struct snd_device *device);28static int snd_hwdep_dev_register(struct snd_device *device);29static int snd_hwdep_dev_disconnect(struct snd_device *device);30 31 32static 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 42static 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 50static 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 59static 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 68static 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(iminor(inode),77 SNDRV_DEVICE_TYPE_HWDEP);78#ifdef CONFIG_SND_OSSEMUL79 } else if (major == SOUND_MAJOR) {80 hw = snd_lookup_oss_minor_data(iminor(inode),81 SNDRV_OSS_DEVICE_TYPE_DMFM);82#endif83 } else84 return -ENXIO;85 if (hw == NULL)86 return -ENODEV;87 88 if (!try_module_get(hw->card->module)) {89 snd_card_unref(hw->card);90 return -EFAULT;91 }92 93 init_waitqueue_entry(&wait, current);94 add_wait_queue(&hw->open_wait, &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 } else114 break;115 set_current_state(TASK_INTERRUPTIBLE);116 mutex_unlock(&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(&hw->open_wait, &wait);129 if (err >= 0) {130 err = snd_card_file_add(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(&hw->open_mutex);140 if (err < 0)141 module_put(hw->card->module);142 snd_card_unref(hw->card);143 return err;144}145 146static 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(hw->card, file);161 module_put(mod);162 return err;163}164 165static __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 173static 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(_info, &info, sizeof(info)))184 return -EFAULT;185 return 0;186}187 188static 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(_info, &info, sizeof(info)))202 return -EFAULT;203 return 0;204}205 206static 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 225static 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(&info, _info, sizeof(info)))231 return -EFAULT;232 return snd_hwdep_dsp_load(hw, &info);233}234 235 236static 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, argp);246 case SNDRV_HWDEP_IOCTL_DSP_STATUS:247 return snd_hwdep_dsp_status(hw, argp);248 case SNDRV_HWDEP_IOCTL_DSP_LOAD:249 return snd_hwdep_dsp_load_user(hw, argp);250 }251 if (hw->ops.ioctl)252 return hw->ops.ioctl(hw, file, cmd, arg);253 return -ENOTTY;254}255 256static 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 264static 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 else282 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(hwdep, info);309 }310 break;311 }312 }313 return -ENOIOCTLCMD;314}315 316#ifdef CONFIG_COMPAT317#include "hwdep_compat.c"318#else319#define snd_hwdep_ioctl_compat NULL320#endif321 322/*323 324 */325 326static 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 340static 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(hwdep->dev);347 kfree(hwdep);348}349 350/**351 * snd_hwdep_new - create a new hwdep instance352 * @card: the card instance353 * @id: the id string354 * @device: the device index (zero-based)355 * @rhwdep: the pointer to store the new hwdep instance356 *357 * Creates a new hwdep instance with the given index on the card.358 * The callbacks (hwdep->ops) must be set on the returned instance359 * after this call manually by the caller.360 *361 * Return: Zero if successful, or a negative error code on failure.362 */363int 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(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(&hwdep->dev, card);390 if (err < 0) {391 snd_hwdep_free(hwdep);392 return err;393 }394 395 dev_set_name(hwdep->dev, "hwC%iD%i", card->number, device);396#ifdef CONFIG_SND_OSSEMUL397 hwdep->oss_type = -1;398#endif399 400 err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &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}410EXPORT_SYMBOL(snd_hwdep_new);411 412static int snd_hwdep_dev_free(struct snd_device *device)413{414 snd_hwdep_free(device->device_data);415 return 0;416}417 418static 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)(®ister_mutex);425 if (snd_hwdep_search(card, hwdep->device))426 return -EBUSY;427 list_add_tail(&hwdep->list, &snd_hwdep_devices);428 err = snd_register_device(SNDRV_DEVICE_TYPE_HWDEP,429 hwdep->card, hwdep->device,430 &snd_hwdep_f_ops, hwdep, hwdep->dev);431 if (err < 0) {432 dev_err(hwdep->dev, "unable to register\n");433 list_del(&hwdep->list);434 return err;435 }436 437#ifdef CONFIG_SND_OSSEMUL438 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(hwdep->oss_type,445 card, hwdep->device,446 &snd_hwdep_f_ops, hwdep) < 0)447 dev_warn(hwdep->dev,448 "unable to register OSS compatibility device\n");449 else450 hwdep->ossreg = 1;451 }452#endif453 return 0;454}455 456static 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)(®ister_mutex);463 if (snd_hwdep_search(hwdep->card, hwdep->device) != hwdep)464 return -EINVAL;465 guard(mutex)(&hwdep->open_mutex);466 wake_up(&hwdep->open_wait);467#ifdef CONFIG_SND_OSSEMUL468 if (hwdep->ossreg)469 snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device);470#endif471 snd_unregister_device(hwdep->dev);472 list_del_init(&hwdep->list);473 return 0;474}475 476#ifdef CONFIG_SND_PROC_FS477/*478 * Info interface479 */480 481static 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)(®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 492static struct snd_info_entry *snd_hwdep_proc_entry;493 494static void __init snd_hwdep_proc_init(void)495{496 struct snd_info_entry *entry;497 498 entry = snd_info_create_module_entry(THIS_MODULE, "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 509static void __exit snd_hwdep_proc_done(void)510{511 snd_info_free_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 functions521 */522 523static int __init alsa_hwdep_init(void)524{525 snd_hwdep_proc_init();526 snd_ctl_register_ioctl(snd_hwdep_control_ioctl);527 snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl);528 return 0;529}530 531static void __exit alsa_hwdep_exit(void)532{533 snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl);534 snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl);535 snd_hwdep_proc_done();536}537 538module_init(alsa_hwdep_init)539module_exit(alsa_hwdep_exit)540