Path: blob/master/sound/firewire/fireworks/fireworks_hwdep.c
29266 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* fireworks_hwdep.c - a part of driver for Fireworks based devices3*4* Copyright (c) 2013-2014 Takashi Sakamoto5*/67/*8* This codes have five functionalities.9*10* 1.get information about firewire node11* 2.get notification about starting/stopping stream12* 3.lock/unlock streaming13* 4.transmit command of EFW transaction14* 5.receive response of EFW transaction15*16*/1718#include "fireworks.h"1920static long21hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,22loff_t *offset)23{24unsigned int length, till_end, type;25struct snd_efw_transaction *t;26u8 *pull_ptr;27long count = 0;2829if (remained < sizeof(type) + sizeof(struct snd_efw_transaction))30return -ENOSPC;3132/* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */33type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE;34if (copy_to_user(buf, &type, sizeof(type)))35return -EFAULT;36count += sizeof(type);37remained -= sizeof(type);38buf += sizeof(type);3940/* write into buffer as many responses as possible */41spin_lock_irq(&efw->lock);4243/*44* When another task reaches here during this task's access to user45* space, it picks up current position in buffer and can read the same46* series of responses.47*/48pull_ptr = efw->pull_ptr;4950while (efw->push_ptr != pull_ptr) {51t = (struct snd_efw_transaction *)(pull_ptr);52length = be32_to_cpu(t->length) * sizeof(__be32);5354/* confirm enough space for this response */55if (remained < length)56break;5758/* copy from ring buffer to user buffer */59while (length > 0) {60till_end = snd_efw_resp_buf_size -61(unsigned int)(pull_ptr - efw->resp_buf);62till_end = min_t(unsigned int, length, till_end);6364spin_unlock_irq(&efw->lock);6566if (copy_to_user(buf, pull_ptr, till_end))67return -EFAULT;6869spin_lock_irq(&efw->lock);7071pull_ptr += till_end;72if (pull_ptr >= efw->resp_buf + snd_efw_resp_buf_size)73pull_ptr -= snd_efw_resp_buf_size;7475length -= till_end;76buf += till_end;77count += till_end;78remained -= till_end;79}80}8182/*83* All of tasks can read from the buffer nearly simultaneously, but the84* last position for each task is different depending on the length of85* given buffer. Here, for simplicity, a position of buffer is set by86* the latest task. It's better for a listening application to allow one87* thread to read from the buffer. Unless, each task can read different88* sequence of responses depending on variation of buffer length.89*/90efw->pull_ptr = pull_ptr;9192spin_unlock_irq(&efw->lock);9394return count;95}9697static long98hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count,99loff_t *offset)100{101union snd_firewire_event event = {102.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,103};104105scoped_guard(spinlock_irq, &efw->lock) {106event.lock_status.status = (efw->dev_lock_count > 0);107efw->dev_lock_changed = false;108}109110count = min_t(long, count, sizeof(event.lock_status));111112if (copy_to_user(buf, &event, count))113return -EFAULT;114115return count;116}117118static long119hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,120loff_t *offset)121{122struct snd_efw *efw = hwdep->private_data;123DEFINE_WAIT(wait);124bool dev_lock_changed;125bool queued;126127spin_lock_irq(&efw->lock);128129dev_lock_changed = efw->dev_lock_changed;130queued = efw->push_ptr != efw->pull_ptr;131132while (!dev_lock_changed && !queued) {133prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);134spin_unlock_irq(&efw->lock);135schedule();136finish_wait(&efw->hwdep_wait, &wait);137if (signal_pending(current))138return -ERESTARTSYS;139spin_lock_irq(&efw->lock);140dev_lock_changed = efw->dev_lock_changed;141queued = efw->push_ptr != efw->pull_ptr;142}143144spin_unlock_irq(&efw->lock);145146if (dev_lock_changed)147count = hwdep_read_locked(efw, buf, count, offset);148else if (queued)149count = hwdep_read_resp_buf(efw, buf, count, offset);150151return count;152}153154static long155hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,156loff_t *offset)157{158struct snd_efw *efw = hwdep->private_data;159u32 seqnum;160u8 *buf;161162if (count < sizeof(struct snd_efw_transaction) ||163SND_EFW_RESPONSE_MAXIMUM_BYTES < count)164return -EINVAL;165166buf = memdup_user(data, count);167if (IS_ERR(buf))168return PTR_ERR(buf);169170/* check seqnum is not for kernel-land */171seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum);172if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) {173count = -EINVAL;174goto end;175}176177if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0)178count = -EIO;179end:180kfree(buf);181return count;182}183184static __poll_t185hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)186{187struct snd_efw *efw = hwdep->private_data;188__poll_t events;189190poll_wait(file, &efw->hwdep_wait, wait);191192guard(spinlock_irq)(&efw->lock);193if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr)194events = EPOLLIN | EPOLLRDNORM;195else196events = 0;197return events | EPOLLOUT;198}199200static int201hwdep_get_info(struct snd_efw *efw, void __user *arg)202{203struct fw_device *dev = fw_parent_device(efw->unit);204struct snd_firewire_get_info info;205206memset(&info, 0, sizeof(info));207info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS;208info.card = dev->card->index;209*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);210*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);211strscpy(info.device_name, dev_name(&dev->device),212sizeof(info.device_name));213214if (copy_to_user(arg, &info, sizeof(info)))215return -EFAULT;216217return 0;218}219220static int221hwdep_lock(struct snd_efw *efw)222{223guard(spinlock_irq)(&efw->lock);224225if (efw->dev_lock_count == 0) {226efw->dev_lock_count = -1;227return 0;228} else {229return -EBUSY;230}231}232233static int234hwdep_unlock(struct snd_efw *efw)235{236guard(spinlock_irq)(&efw->lock);237238if (efw->dev_lock_count == -1) {239efw->dev_lock_count = 0;240return 0;241} else {242return -EBADFD;243}244}245246static int247hwdep_release(struct snd_hwdep *hwdep, struct file *file)248{249struct snd_efw *efw = hwdep->private_data;250251guard(spinlock_irq)(&efw->lock);252if (efw->dev_lock_count == -1)253efw->dev_lock_count = 0;254255return 0;256}257258static int259hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,260unsigned int cmd, unsigned long arg)261{262struct snd_efw *efw = hwdep->private_data;263264switch (cmd) {265case SNDRV_FIREWIRE_IOCTL_GET_INFO:266return hwdep_get_info(efw, (void __user *)arg);267case SNDRV_FIREWIRE_IOCTL_LOCK:268return hwdep_lock(efw);269case SNDRV_FIREWIRE_IOCTL_UNLOCK:270return hwdep_unlock(efw);271default:272return -ENOIOCTLCMD;273}274}275276#ifdef CONFIG_COMPAT277static int278hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,279unsigned int cmd, unsigned long arg)280{281return hwdep_ioctl(hwdep, file, cmd,282(unsigned long)compat_ptr(arg));283}284#else285#define hwdep_compat_ioctl NULL286#endif287288int snd_efw_create_hwdep_device(struct snd_efw *efw)289{290static const struct snd_hwdep_ops ops = {291.read = hwdep_read,292.write = hwdep_write,293.release = hwdep_release,294.poll = hwdep_poll,295.ioctl = hwdep_ioctl,296.ioctl_compat = hwdep_compat_ioctl,297};298struct snd_hwdep *hwdep;299int err;300301err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);302if (err < 0)303goto end;304strscpy(hwdep->name, "Fireworks");305hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;306hwdep->ops = ops;307hwdep->private_data = efw;308hwdep->exclusive = true;309end:310return err;311}312313314315