linux/sound/core/hwdep.c
<<
>>
Prefs
   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
  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_OSSEMUL
  79        } else if (major == SOUND_MAJOR) {
  80                hw = snd_lookup_oss_minor_data(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(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                } else
 114                        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        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(&hw->open_mutex);
 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        strlcpy(info.id, hw->id, sizeof(info.id));      
 181        strlcpy(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        if ((err = hw->ops.dsp_status(hw, &info)) < 0)
 199                return err;
 200        if (copy_to_user(_info, &info, sizeof(info)))
 201                return -EFAULT;
 202        return 0;
 203}
 204
 205static int snd_hwdep_dsp_load(struct snd_hwdep *hw,
 206                              struct snd_hwdep_dsp_image __user *_info)
 207{
 208        struct snd_hwdep_dsp_image info;
 209        int err;
 210        
 211        if (! hw->ops.dsp_load)
 212                return -ENXIO;
 213        memset(&info, 0, sizeof(info));
 214        if (copy_from_user(&info, _info, sizeof(info)))
 215                return -EFAULT;
 216        if (info.index >= 32)
 217                return -EINVAL;
 218        /* check whether the dsp was already loaded */
 219        if (hw->dsp_loaded & (1 << info.index))
 220                return -EBUSY;
 221        err = hw->ops.dsp_load(hw, &info);
 222        if (err < 0)
 223                return err;
 224        hw->dsp_loaded |= (1 << info.index);
 225        return 0;
 226}
 227
 228static long snd_hwdep_ioctl(struct file * file, unsigned int cmd,
 229                            unsigned long arg)
 230{
 231        struct snd_hwdep *hw = file->private_data;
 232        void __user *argp = (void __user *)arg;
 233        switch (cmd) {
 234        case SNDRV_HWDEP_IOCTL_PVERSION:
 235                return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp);
 236        case SNDRV_HWDEP_IOCTL_INFO:
 237                return snd_hwdep_info(hw, argp);
 238        case SNDRV_HWDEP_IOCTL_DSP_STATUS:
 239                return snd_hwdep_dsp_status(hw, argp);
 240        case SNDRV_HWDEP_IOCTL_DSP_LOAD:
 241                return snd_hwdep_dsp_load(hw, argp);
 242        }
 243        if (hw->ops.ioctl)
 244                return hw->ops.ioctl(hw, file, cmd, arg);
 245        return -ENOTTY;
 246}
 247
 248static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma)
 249{
 250        struct snd_hwdep *hw = file->private_data;
 251        if (hw->ops.mmap)
 252                return hw->ops.mmap(hw, file, vma);
 253        return -ENXIO;
 254}
 255
 256static int snd_hwdep_control_ioctl(struct snd_card *card,
 257                                   struct snd_ctl_file * control,
 258                                   unsigned int cmd, unsigned long arg)
 259{
 260        switch (cmd) {
 261        case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE:
 262                {
 263                        int device;
 264
 265                        if (get_user(device, (int __user *)arg))
 266                                return -EFAULT;
 267                        mutex_lock(&register_mutex);
 268
 269                        if (device < 0)
 270                                device = 0;
 271                        else if (device < SNDRV_MINOR_HWDEPS)
 272                                device++;
 273                        else
 274                                device = SNDRV_MINOR_HWDEPS;
 275
 276                        while (device < SNDRV_MINOR_HWDEPS) {
 277                                if (snd_hwdep_search(card, device))
 278                                        break;
 279                                device++;
 280                        }
 281                        if (device >= SNDRV_MINOR_HWDEPS)
 282                                device = -1;
 283                        mutex_unlock(&register_mutex);
 284                        if (put_user(device, (int __user *)arg))
 285                                return -EFAULT;
 286                        return 0;
 287                }
 288        case SNDRV_CTL_IOCTL_HWDEP_INFO:
 289                {
 290                        struct snd_hwdep_info __user *info = (struct snd_hwdep_info __user *)arg;
 291                        int device, err;
 292                        struct snd_hwdep *hwdep;
 293
 294                        if (get_user(device, &info->device))
 295                                return -EFAULT;
 296                        mutex_lock(&register_mutex);
 297                        hwdep = snd_hwdep_search(card, device);
 298                        if (hwdep)
 299                                err = snd_hwdep_info(hwdep, info);
 300                        else
 301                                err = -ENXIO;
 302                        mutex_unlock(&register_mutex);
 303                        return err;
 304                }
 305        }
 306        return -ENOIOCTLCMD;
 307}
 308
 309#ifdef CONFIG_COMPAT
 310#include "hwdep_compat.c"
 311#else
 312#define snd_hwdep_ioctl_compat  NULL
 313#endif
 314
 315/*
 316
 317 */
 318
 319static const struct file_operations snd_hwdep_f_ops =
 320{
 321        .owner =        THIS_MODULE,
 322        .llseek =       snd_hwdep_llseek,
 323        .read =         snd_hwdep_read,
 324        .write =        snd_hwdep_write,
 325        .open =         snd_hwdep_open,
 326        .release =      snd_hwdep_release,
 327        .poll =         snd_hwdep_poll,
 328        .unlocked_ioctl =       snd_hwdep_ioctl,
 329        .compat_ioctl = snd_hwdep_ioctl_compat,
 330        .mmap =         snd_hwdep_mmap,
 331};
 332
 333static void release_hwdep_device(struct device *dev)
 334{
 335        kfree(container_of(dev, struct snd_hwdep, dev));
 336}
 337
 338/**
 339 * snd_hwdep_new - create a new hwdep instance
 340 * @card: the card instance
 341 * @id: the id string
 342 * @device: the device index (zero-based)
 343 * @rhwdep: the pointer to store the new hwdep instance
 344 *
 345 * Creates a new hwdep instance with the given index on the card.
 346 * The callbacks (hwdep->ops) must be set on the returned instance
 347 * after this call manually by the caller.
 348 *
 349 * Return: Zero if successful, or a negative error code on failure.
 350 */
 351int snd_hwdep_new(struct snd_card *card, char *id, int device,
 352                  struct snd_hwdep **rhwdep)
 353{
 354        struct snd_hwdep *hwdep;
 355        int err;
 356        static struct snd_device_ops ops = {
 357                .dev_free = snd_hwdep_dev_free,
 358                .dev_register = snd_hwdep_dev_register,
 359                .dev_disconnect = snd_hwdep_dev_disconnect,
 360        };
 361
 362        if (snd_BUG_ON(!card))
 363                return -ENXIO;
 364        if (rhwdep)
 365                *rhwdep = NULL;
 366        hwdep = kzalloc(sizeof(*hwdep), GFP_KERNEL);
 367        if (!hwdep)
 368                return -ENOMEM;
 369
 370        init_waitqueue_head(&hwdep->open_wait);
 371        mutex_init(&hwdep->open_mutex);
 372        hwdep->card = card;
 373        hwdep->device = device;
 374        if (id)
 375                strlcpy(hwdep->id, id, sizeof(hwdep->id));
 376
 377        snd_device_initialize(&hwdep->dev, card);
 378        hwdep->dev.release = release_hwdep_device;
 379        dev_set_name(&hwdep->dev, "hwC%iD%i", card->number, device);
 380#ifdef CONFIG_SND_OSSEMUL
 381        hwdep->oss_type = -1;
 382#endif
 383
 384        err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops);
 385        if (err < 0) {
 386                put_device(&hwdep->dev);
 387                return err;
 388        }
 389
 390        if (rhwdep)
 391                *rhwdep = hwdep;
 392        return 0;
 393}
 394EXPORT_SYMBOL(snd_hwdep_new);
 395
 396static int snd_hwdep_dev_free(struct snd_device *device)
 397{
 398        struct snd_hwdep *hwdep = device->device_data;
 399        if (!hwdep)
 400                return 0;
 401        if (hwdep->private_free)
 402                hwdep->private_free(hwdep);
 403        put_device(&hwdep->dev);
 404        return 0;
 405}
 406
 407static int snd_hwdep_dev_register(struct snd_device *device)
 408{
 409        struct snd_hwdep *hwdep = device->device_data;
 410        struct snd_card *card = hwdep->card;
 411        int err;
 412
 413        mutex_lock(&register_mutex);
 414        if (snd_hwdep_search(card, hwdep->device)) {
 415                mutex_unlock(&register_mutex);
 416                return -EBUSY;
 417        }
 418        list_add_tail(&hwdep->list, &snd_hwdep_devices);
 419        err = snd_register_device(SNDRV_DEVICE_TYPE_HWDEP,
 420                                  hwdep->card, hwdep->device,
 421                                  &snd_hwdep_f_ops, hwdep, &hwdep->dev);
 422        if (err < 0) {
 423                dev_err(&hwdep->dev, "unable to register\n");
 424                list_del(&hwdep->list);
 425                mutex_unlock(&register_mutex);
 426                return err;
 427        }
 428
 429#ifdef CONFIG_SND_OSSEMUL
 430        hwdep->ossreg = 0;
 431        if (hwdep->oss_type >= 0) {
 432                if (hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM &&
 433                    hwdep->device)
 434                        dev_warn(&hwdep->dev,
 435                                 "only hwdep device 0 can be registered as OSS direct FM device!\n");
 436                else if (snd_register_oss_device(hwdep->oss_type,
 437                                                 card, hwdep->device,
 438                                                 &snd_hwdep_f_ops, hwdep) < 0)
 439                        dev_warn(&hwdep->dev,
 440                                 "unable to register OSS compatibility device\n");
 441                else
 442                        hwdep->ossreg = 1;
 443        }
 444#endif
 445        mutex_unlock(&register_mutex);
 446        return 0;
 447}
 448
 449static int snd_hwdep_dev_disconnect(struct snd_device *device)
 450{
 451        struct snd_hwdep *hwdep = device->device_data;
 452
 453        if (snd_BUG_ON(!hwdep))
 454                return -ENXIO;
 455        mutex_lock(&register_mutex);
 456        if (snd_hwdep_search(hwdep->card, hwdep->device) != hwdep) {
 457                mutex_unlock(&register_mutex);
 458                return -EINVAL;
 459        }
 460        mutex_lock(&hwdep->open_mutex);
 461        wake_up(&hwdep->open_wait);
 462#ifdef CONFIG_SND_OSSEMUL
 463        if (hwdep->ossreg)
 464                snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device);
 465#endif
 466        snd_unregister_device(&hwdep->dev);
 467        list_del_init(&hwdep->list);
 468        mutex_unlock(&hwdep->open_mutex);
 469        mutex_unlock(&register_mutex);
 470        return 0;
 471}
 472
 473#ifdef CONFIG_SND_PROC_FS
 474/*
 475 *  Info interface
 476 */
 477
 478static void snd_hwdep_proc_read(struct snd_info_entry *entry,
 479                                struct snd_info_buffer *buffer)
 480{
 481        struct snd_hwdep *hwdep;
 482
 483        mutex_lock(&register_mutex);
 484        list_for_each_entry(hwdep, &snd_hwdep_devices, list)
 485                snd_iprintf(buffer, "%02i-%02i: %s\n",
 486                            hwdep->card->number, hwdep->device, hwdep->name);
 487        mutex_unlock(&register_mutex);
 488}
 489
 490static struct snd_info_entry *snd_hwdep_proc_entry;
 491
 492static void __init snd_hwdep_proc_init(void)
 493{
 494        struct snd_info_entry *entry;
 495
 496        if ((entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL)) != NULL) {
 497                entry->c.text.read = snd_hwdep_proc_read;
 498                if (snd_info_register(entry) < 0) {
 499                        snd_info_free_entry(entry);
 500                        entry = NULL;
 501                }
 502        }
 503        snd_hwdep_proc_entry = entry;
 504}
 505
 506static void __exit snd_hwdep_proc_done(void)
 507{
 508        snd_info_free_entry(snd_hwdep_proc_entry);
 509}
 510#else /* !CONFIG_SND_PROC_FS */
 511#define snd_hwdep_proc_init()
 512#define snd_hwdep_proc_done()
 513#endif /* CONFIG_SND_PROC_FS */
 514
 515
 516/*
 517 *  ENTRY functions
 518 */
 519
 520static int __init alsa_hwdep_init(void)
 521{
 522        snd_hwdep_proc_init();
 523        snd_ctl_register_ioctl(snd_hwdep_control_ioctl);
 524        snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl);
 525        return 0;
 526}
 527
 528static void __exit alsa_hwdep_exit(void)
 529{
 530        snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl);
 531        snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl);
 532        snd_hwdep_proc_done();
 533}
 534
 535module_init(alsa_hwdep_init)
 536module_exit(alsa_hwdep_exit)
 537