| /* | 
|  * bebob_hwdep.c - a part of driver for BeBoB based devices | 
|  * | 
|  * Copyright (c) 2013-2014 Takashi Sakamoto | 
|  * | 
|  * Licensed under the terms of the GNU General Public License, version 2. | 
|  */ | 
|   | 
| /* | 
|  * This codes give three functionality. | 
|  * | 
|  * 1.get firewire node infomation | 
|  * 2.get notification about starting/stopping stream | 
|  * 3.lock/unlock stream | 
|  */ | 
|   | 
| #include "bebob.h" | 
|   | 
| static long | 
| hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count, | 
|        loff_t *offset) | 
| { | 
|     struct snd_bebob *bebob = hwdep->private_data; | 
|     DEFINE_WAIT(wait); | 
|     union snd_firewire_event event; | 
|   | 
|     spin_lock_irq(&bebob->lock); | 
|   | 
|     while (!bebob->dev_lock_changed) { | 
|         prepare_to_wait(&bebob->hwdep_wait, &wait, TASK_INTERRUPTIBLE); | 
|         spin_unlock_irq(&bebob->lock); | 
|         schedule(); | 
|         finish_wait(&bebob->hwdep_wait, &wait); | 
|         if (signal_pending(current)) | 
|             return -ERESTARTSYS; | 
|         spin_lock_irq(&bebob->lock); | 
|     } | 
|   | 
|     memset(&event, 0, sizeof(event)); | 
|     count = min_t(long, count, sizeof(event.lock_status)); | 
|     if (bebob->dev_lock_changed) { | 
|         event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; | 
|         event.lock_status.status = (bebob->dev_lock_count > 0); | 
|         bebob->dev_lock_changed = false; | 
|     } | 
|   | 
|     spin_unlock_irq(&bebob->lock); | 
|   | 
|     if (copy_to_user(buf, &event, count)) | 
|         return -EFAULT; | 
|   | 
|     return count; | 
| } | 
|   | 
| static __poll_t | 
| hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) | 
| { | 
|     struct snd_bebob *bebob = hwdep->private_data; | 
|     __poll_t events; | 
|   | 
|     poll_wait(file, &bebob->hwdep_wait, wait); | 
|   | 
|     spin_lock_irq(&bebob->lock); | 
|     if (bebob->dev_lock_changed) | 
|         events = EPOLLIN | EPOLLRDNORM; | 
|     else | 
|         events = 0; | 
|     spin_unlock_irq(&bebob->lock); | 
|   | 
|     return events; | 
| } | 
|   | 
| static int | 
| hwdep_get_info(struct snd_bebob *bebob, void __user *arg) | 
| { | 
|     struct fw_device *dev = fw_parent_device(bebob->unit); | 
|     struct snd_firewire_get_info info; | 
|   | 
|     memset(&info, 0, sizeof(info)); | 
|     info.type = SNDRV_FIREWIRE_TYPE_BEBOB; | 
|     info.card = dev->card->index; | 
|     *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); | 
|     *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); | 
|     strlcpy(info.device_name, dev_name(&dev->device), | 
|         sizeof(info.device_name)); | 
|   | 
|     if (copy_to_user(arg, &info, sizeof(info))) | 
|         return -EFAULT; | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int | 
| hwdep_lock(struct snd_bebob *bebob) | 
| { | 
|     int err; | 
|   | 
|     spin_lock_irq(&bebob->lock); | 
|   | 
|     if (bebob->dev_lock_count == 0) { | 
|         bebob->dev_lock_count = -1; | 
|         err = 0; | 
|     } else { | 
|         err = -EBUSY; | 
|     } | 
|   | 
|     spin_unlock_irq(&bebob->lock); | 
|   | 
|     return err; | 
| } | 
|   | 
| static int | 
| hwdep_unlock(struct snd_bebob *bebob) | 
| { | 
|     int err; | 
|   | 
|     spin_lock_irq(&bebob->lock); | 
|   | 
|     if (bebob->dev_lock_count == -1) { | 
|         bebob->dev_lock_count = 0; | 
|         err = 0; | 
|     } else { | 
|         err = -EBADFD; | 
|     } | 
|   | 
|     spin_unlock_irq(&bebob->lock); | 
|   | 
|     return err; | 
| } | 
|   | 
| static int | 
| hwdep_release(struct snd_hwdep *hwdep, struct file *file) | 
| { | 
|     struct snd_bebob *bebob = hwdep->private_data; | 
|   | 
|     spin_lock_irq(&bebob->lock); | 
|     if (bebob->dev_lock_count == -1) | 
|         bebob->dev_lock_count = 0; | 
|     spin_unlock_irq(&bebob->lock); | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int | 
| hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, | 
|         unsigned int cmd, unsigned long arg) | 
| { | 
|     struct snd_bebob *bebob = hwdep->private_data; | 
|   | 
|     switch (cmd) { | 
|     case SNDRV_FIREWIRE_IOCTL_GET_INFO: | 
|         return hwdep_get_info(bebob, (void __user *)arg); | 
|     case SNDRV_FIREWIRE_IOCTL_LOCK: | 
|         return hwdep_lock(bebob); | 
|     case SNDRV_FIREWIRE_IOCTL_UNLOCK: | 
|         return hwdep_unlock(bebob); | 
|     default: | 
|         return -ENOIOCTLCMD; | 
|     } | 
| } | 
|   | 
| #ifdef CONFIG_COMPAT | 
| static int | 
| hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, | 
|            unsigned int cmd, unsigned long arg) | 
| { | 
|     return hwdep_ioctl(hwdep, file, cmd, | 
|                (unsigned long)compat_ptr(arg)); | 
| } | 
| #else | 
| #define hwdep_compat_ioctl NULL | 
| #endif | 
|   | 
| int snd_bebob_create_hwdep_device(struct snd_bebob *bebob) | 
| { | 
|     static const struct snd_hwdep_ops ops = { | 
|         .read        = hwdep_read, | 
|         .release    = hwdep_release, | 
|         .poll        = hwdep_poll, | 
|         .ioctl        = hwdep_ioctl, | 
|         .ioctl_compat    = hwdep_compat_ioctl, | 
|     }; | 
|     struct snd_hwdep *hwdep; | 
|     int err; | 
|   | 
|     err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep); | 
|     if (err < 0) | 
|         goto end; | 
|     strcpy(hwdep->name, "BeBoB"); | 
|     hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB; | 
|     hwdep->ops = ops; | 
|     hwdep->private_data = bebob; | 
|     hwdep->exclusive = true; | 
| end: | 
|     return err; | 
| } |