Path: blob/master/sound/firewire/fireworks/fireworks_hwdep.c
26424 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};104105spin_lock_irq(&efw->lock);106107event.lock_status.status = (efw->dev_lock_count > 0);108efw->dev_lock_changed = false;109110spin_unlock_irq(&efw->lock);111112count = min_t(long, count, sizeof(event.lock_status));113114if (copy_to_user(buf, &event, count))115return -EFAULT;116117return count;118}119120static long121hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,122loff_t *offset)123{124struct snd_efw *efw = hwdep->private_data;125DEFINE_WAIT(wait);126bool dev_lock_changed;127bool queued;128129spin_lock_irq(&efw->lock);130131dev_lock_changed = efw->dev_lock_changed;132queued = efw->push_ptr != efw->pull_ptr;133134while (!dev_lock_changed && !queued) {135prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);136spin_unlock_irq(&efw->lock);137schedule();138finish_wait(&efw->hwdep_wait, &wait);139if (signal_pending(current))140return -ERESTARTSYS;141spin_lock_irq(&efw->lock);142dev_lock_changed = efw->dev_lock_changed;143queued = efw->push_ptr != efw->pull_ptr;144}145146spin_unlock_irq(&efw->lock);147148if (dev_lock_changed)149count = hwdep_read_locked(efw, buf, count, offset);150else if (queued)151count = hwdep_read_resp_buf(efw, buf, count, offset);152153return count;154}155156static long157hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,158loff_t *offset)159{160struct snd_efw *efw = hwdep->private_data;161u32 seqnum;162u8 *buf;163164if (count < sizeof(struct snd_efw_transaction) ||165SND_EFW_RESPONSE_MAXIMUM_BYTES < count)166return -EINVAL;167168buf = memdup_user(data, count);169if (IS_ERR(buf))170return PTR_ERR(buf);171172/* check seqnum is not for kernel-land */173seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum);174if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) {175count = -EINVAL;176goto end;177}178179if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0)180count = -EIO;181end:182kfree(buf);183return count;184}185186static __poll_t187hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)188{189struct snd_efw *efw = hwdep->private_data;190__poll_t events;191192poll_wait(file, &efw->hwdep_wait, wait);193194spin_lock_irq(&efw->lock);195if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr)196events = EPOLLIN | EPOLLRDNORM;197else198events = 0;199spin_unlock_irq(&efw->lock);200201return events | EPOLLOUT;202}203204static int205hwdep_get_info(struct snd_efw *efw, void __user *arg)206{207struct fw_device *dev = fw_parent_device(efw->unit);208struct snd_firewire_get_info info;209210memset(&info, 0, sizeof(info));211info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS;212info.card = dev->card->index;213*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);214*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);215strscpy(info.device_name, dev_name(&dev->device),216sizeof(info.device_name));217218if (copy_to_user(arg, &info, sizeof(info)))219return -EFAULT;220221return 0;222}223224static int225hwdep_lock(struct snd_efw *efw)226{227int err;228229spin_lock_irq(&efw->lock);230231if (efw->dev_lock_count == 0) {232efw->dev_lock_count = -1;233err = 0;234} else {235err = -EBUSY;236}237238spin_unlock_irq(&efw->lock);239240return err;241}242243static int244hwdep_unlock(struct snd_efw *efw)245{246int err;247248spin_lock_irq(&efw->lock);249250if (efw->dev_lock_count == -1) {251efw->dev_lock_count = 0;252err = 0;253} else {254err = -EBADFD;255}256257spin_unlock_irq(&efw->lock);258259return err;260}261262static int263hwdep_release(struct snd_hwdep *hwdep, struct file *file)264{265struct snd_efw *efw = hwdep->private_data;266267spin_lock_irq(&efw->lock);268if (efw->dev_lock_count == -1)269efw->dev_lock_count = 0;270spin_unlock_irq(&efw->lock);271272return 0;273}274275static int276hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,277unsigned int cmd, unsigned long arg)278{279struct snd_efw *efw = hwdep->private_data;280281switch (cmd) {282case SNDRV_FIREWIRE_IOCTL_GET_INFO:283return hwdep_get_info(efw, (void __user *)arg);284case SNDRV_FIREWIRE_IOCTL_LOCK:285return hwdep_lock(efw);286case SNDRV_FIREWIRE_IOCTL_UNLOCK:287return hwdep_unlock(efw);288default:289return -ENOIOCTLCMD;290}291}292293#ifdef CONFIG_COMPAT294static int295hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,296unsigned int cmd, unsigned long arg)297{298return hwdep_ioctl(hwdep, file, cmd,299(unsigned long)compat_ptr(arg));300}301#else302#define hwdep_compat_ioctl NULL303#endif304305int snd_efw_create_hwdep_device(struct snd_efw *efw)306{307static const struct snd_hwdep_ops ops = {308.read = hwdep_read,309.write = hwdep_write,310.release = hwdep_release,311.poll = hwdep_poll,312.ioctl = hwdep_ioctl,313.ioctl_compat = hwdep_compat_ioctl,314};315struct snd_hwdep *hwdep;316int err;317318err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);319if (err < 0)320goto end;321strscpy(hwdep->name, "Fireworks");322hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;323hwdep->ops = ops;324hwdep->private_data = efw;325hwdep->exclusive = true;326end:327return err;328}329330331332