| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * ff-hwdep.c - a part of driver for RME Fireface series | |
| * | |
| * Copyright (c) 2015-2017 Takashi Sakamoto | |
| */ | |
| /* | |
| * This codes give three functionality. | |
| * | |
| * 1.get firewire node information | |
| * 2.get notification about starting/stopping stream | |
| * 3.lock/unlock stream | |
| */ | |
| #include "ff.h" | |
| static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, | |
| loff_t *offset) | |
| { | |
| struct snd_ff *ff = hwdep->private_data; | |
| DEFINE_WAIT(wait); | |
| union snd_firewire_event event; | |
| spin_lock_irq(&ff->lock); | |
| while (!ff->dev_lock_changed) { | |
| prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE); | |
| spin_unlock_irq(&ff->lock); | |
| schedule(); | |
| finish_wait(&ff->hwdep_wait, &wait); | |
| if (signal_pending(current)) | |
| return -ERESTARTSYS; | |
| spin_lock_irq(&ff->lock); | |
| } | |
| memset(&event, 0, sizeof(event)); | |
| if (ff->dev_lock_changed) { | |
| event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; | |
| event.lock_status.status = (ff->dev_lock_count > 0); | |
| ff->dev_lock_changed = false; | |
| count = min_t(long, count, sizeof(event.lock_status)); | |
| } | |
| spin_unlock_irq(&ff->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_ff *ff = hwdep->private_data; | |
| __poll_t events; | |
| poll_wait(file, &ff->hwdep_wait, wait); | |
| spin_lock_irq(&ff->lock); | |
| if (ff->dev_lock_changed) | |
| events = EPOLLIN | EPOLLRDNORM; | |
| else | |
| events = 0; | |
| spin_unlock_irq(&ff->lock); | |
| return events; | |
| } | |
| static int hwdep_get_info(struct snd_ff *ff, void __user *arg) | |
| { | |
| struct fw_device *dev = fw_parent_device(ff->unit); | |
| struct snd_firewire_get_info info; | |
| memset(&info, 0, sizeof(info)); | |
| info.type = SNDRV_FIREWIRE_TYPE_FIREFACE; | |
| 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_ff *ff) | |
| { | |
| int err; | |
| spin_lock_irq(&ff->lock); | |
| if (ff->dev_lock_count == 0) { | |
| ff->dev_lock_count = -1; | |
| err = 0; | |
| } else { | |
| err = -EBUSY; | |
| } | |
| spin_unlock_irq(&ff->lock); | |
| return err; | |
| } | |
| static int hwdep_unlock(struct snd_ff *ff) | |
| { | |
| int err; | |
| spin_lock_irq(&ff->lock); | |
| if (ff->dev_lock_count == -1) { | |
| ff->dev_lock_count = 0; | |
| err = 0; | |
| } else { | |
| err = -EBADFD; | |
| } | |
| spin_unlock_irq(&ff->lock); | |
| return err; | |
| } | |
| static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) | |
| { | |
| struct snd_ff *ff = hwdep->private_data; | |
| spin_lock_irq(&ff->lock); | |
| if (ff->dev_lock_count == -1) | |
| ff->dev_lock_count = 0; | |
| spin_unlock_irq(&ff->lock); | |
| return 0; | |
| } | |
| static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, | |
| unsigned int cmd, unsigned long arg) | |
| { | |
| struct snd_ff *ff = hwdep->private_data; | |
| switch (cmd) { | |
| case SNDRV_FIREWIRE_IOCTL_GET_INFO: | |
| return hwdep_get_info(ff, (void __user *)arg); | |
| case SNDRV_FIREWIRE_IOCTL_LOCK: | |
| return hwdep_lock(ff); | |
| case SNDRV_FIREWIRE_IOCTL_UNLOCK: | |
| return hwdep_unlock(ff); | |
| 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_ff_create_hwdep_devices(struct snd_ff *ff) | |
| { | |
| static const struct snd_hwdep_ops hwdep_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(ff->card, ff->card->driver, 0, &hwdep); | |
| if (err < 0) | |
| return err; | |
| strcpy(hwdep->name, ff->card->driver); | |
| hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE; | |
| hwdep->ops = hwdep_ops; | |
| hwdep->private_data = ff; | |
| hwdep->exclusive = true; | |
| return 0; | |
| } |