brintos

brintos / linux-shallow public Read only

0
0
Text · 7.3 KiB · 3a53914 Raw
331 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * fireworks_hwdep.c - a part of driver for Fireworks based devices4 *5 * Copyright (c) 2013-2014 Takashi Sakamoto6 */7 8/*9 * This codes have five functionalities.10 *11 * 1.get information about firewire node12 * 2.get notification about starting/stopping stream13 * 3.lock/unlock streaming14 * 4.transmit command of EFW transaction15 * 5.receive response of EFW transaction16 *17 */18 19#include "fireworks.h"20 21static long22hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,23		    loff_t *offset)24{25	unsigned int length, till_end, type;26	struct snd_efw_transaction *t;27	u8 *pull_ptr;28	long count = 0;29 30	if (remained < sizeof(type) + sizeof(struct snd_efw_transaction))31		return -ENOSPC;32 33	/* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */34	type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE;35	if (copy_to_user(buf, &type, sizeof(type)))36		return -EFAULT;37	count += sizeof(type);38	remained -= sizeof(type);39	buf += sizeof(type);40 41	/* write into buffer as many responses as possible */42	spin_lock_irq(&efw->lock);43 44	/*45	 * When another task reaches here during this task's access to user46	 * space, it picks up current position in buffer and can read the same47	 * series of responses.48	 */49	pull_ptr = efw->pull_ptr;50 51	while (efw->push_ptr != pull_ptr) {52		t = (struct snd_efw_transaction *)(pull_ptr);53		length = be32_to_cpu(t->length) * sizeof(__be32);54 55		/* confirm enough space for this response */56		if (remained < length)57			break;58 59		/* copy from ring buffer to user buffer */60		while (length > 0) {61			till_end = snd_efw_resp_buf_size -62				(unsigned int)(pull_ptr - efw->resp_buf);63			till_end = min_t(unsigned int, length, till_end);64 65			spin_unlock_irq(&efw->lock);66 67			if (copy_to_user(buf, pull_ptr, till_end))68				return -EFAULT;69 70			spin_lock_irq(&efw->lock);71 72			pull_ptr += till_end;73			if (pull_ptr >= efw->resp_buf + snd_efw_resp_buf_size)74				pull_ptr -= snd_efw_resp_buf_size;75 76			length -= till_end;77			buf += till_end;78			count += till_end;79			remained -= till_end;80		}81	}82 83	/*84	 * All of tasks can read from the buffer nearly simultaneously, but the85	 * last position for each task is different depending on the length of86	 * given buffer. Here, for simplicity, a position of buffer is set by87	 * the latest task. It's better for a listening application to allow one88	 * thread to read from the buffer. Unless, each task can read different89	 * sequence of responses depending on variation of buffer length.90	 */91	efw->pull_ptr = pull_ptr;92 93	spin_unlock_irq(&efw->lock);94 95	return count;96}97 98static long99hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count,100		  loff_t *offset)101{102	union snd_firewire_event event = {103		.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,104	};105 106	spin_lock_irq(&efw->lock);107 108	event.lock_status.status = (efw->dev_lock_count > 0);109	efw->dev_lock_changed = false;110 111	spin_unlock_irq(&efw->lock);112 113	count = min_t(long, count, sizeof(event.lock_status));114 115	if (copy_to_user(buf, &event, count))116		return -EFAULT;117 118	return count;119}120 121static long122hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,123	   loff_t *offset)124{125	struct snd_efw *efw = hwdep->private_data;126	DEFINE_WAIT(wait);127	bool dev_lock_changed;128	bool queued;129 130	spin_lock_irq(&efw->lock);131 132	dev_lock_changed = efw->dev_lock_changed;133	queued = efw->push_ptr != efw->pull_ptr;134 135	while (!dev_lock_changed && !queued) {136		prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);137		spin_unlock_irq(&efw->lock);138		schedule();139		finish_wait(&efw->hwdep_wait, &wait);140		if (signal_pending(current))141			return -ERESTARTSYS;142		spin_lock_irq(&efw->lock);143		dev_lock_changed = efw->dev_lock_changed;144		queued = efw->push_ptr != efw->pull_ptr;145	}146 147	spin_unlock_irq(&efw->lock);148 149	if (dev_lock_changed)150		count = hwdep_read_locked(efw, buf, count, offset);151	else if (queued)152		count = hwdep_read_resp_buf(efw, buf, count, offset);153 154	return count;155}156 157static long158hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,159	    loff_t *offset)160{161	struct snd_efw *efw = hwdep->private_data;162	u32 seqnum;163	u8 *buf;164 165	if (count < sizeof(struct snd_efw_transaction) ||166	    SND_EFW_RESPONSE_MAXIMUM_BYTES < count)167		return -EINVAL;168 169	buf = memdup_user(data, count);170	if (IS_ERR(buf))171		return PTR_ERR(buf);172 173	/* check seqnum is not for kernel-land */174	seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum);175	if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) {176		count = -EINVAL;177		goto end;178	}179 180	if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0)181		count = -EIO;182end:183	kfree(buf);184	return count;185}186 187static __poll_t188hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)189{190	struct snd_efw *efw = hwdep->private_data;191	__poll_t events;192 193	poll_wait(file, &efw->hwdep_wait, wait);194 195	spin_lock_irq(&efw->lock);196	if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr)197		events = EPOLLIN | EPOLLRDNORM;198	else199		events = 0;200	spin_unlock_irq(&efw->lock);201 202	return events | EPOLLOUT;203}204 205static int206hwdep_get_info(struct snd_efw *efw, void __user *arg)207{208	struct fw_device *dev = fw_parent_device(efw->unit);209	struct snd_firewire_get_info info;210 211	memset(&info, 0, sizeof(info));212	info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS;213	info.card = dev->card->index;214	*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);215	*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);216	strscpy(info.device_name, dev_name(&dev->device),217		sizeof(info.device_name));218 219	if (copy_to_user(arg, &info, sizeof(info)))220		return -EFAULT;221 222	return 0;223}224 225static int226hwdep_lock(struct snd_efw *efw)227{228	int err;229 230	spin_lock_irq(&efw->lock);231 232	if (efw->dev_lock_count == 0) {233		efw->dev_lock_count = -1;234		err = 0;235	} else {236		err = -EBUSY;237	}238 239	spin_unlock_irq(&efw->lock);240 241	return err;242}243 244static int245hwdep_unlock(struct snd_efw *efw)246{247	int err;248 249	spin_lock_irq(&efw->lock);250 251	if (efw->dev_lock_count == -1) {252		efw->dev_lock_count = 0;253		err = 0;254	} else {255		err = -EBADFD;256	}257 258	spin_unlock_irq(&efw->lock);259 260	return err;261}262 263static int264hwdep_release(struct snd_hwdep *hwdep, struct file *file)265{266	struct snd_efw *efw = hwdep->private_data;267 268	spin_lock_irq(&efw->lock);269	if (efw->dev_lock_count == -1)270		efw->dev_lock_count = 0;271	spin_unlock_irq(&efw->lock);272 273	return 0;274}275 276static int277hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,278	    unsigned int cmd, unsigned long arg)279{280	struct snd_efw *efw = hwdep->private_data;281 282	switch (cmd) {283	case SNDRV_FIREWIRE_IOCTL_GET_INFO:284		return hwdep_get_info(efw, (void __user *)arg);285	case SNDRV_FIREWIRE_IOCTL_LOCK:286		return hwdep_lock(efw);287	case SNDRV_FIREWIRE_IOCTL_UNLOCK:288		return hwdep_unlock(efw);289	default:290		return -ENOIOCTLCMD;291	}292}293 294#ifdef CONFIG_COMPAT295static int296hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,297		   unsigned int cmd, unsigned long arg)298{299	return hwdep_ioctl(hwdep, file, cmd,300			   (unsigned long)compat_ptr(arg));301}302#else303#define hwdep_compat_ioctl NULL304#endif305 306int snd_efw_create_hwdep_device(struct snd_efw *efw)307{308	static const struct snd_hwdep_ops ops = {309		.read		= hwdep_read,310		.write		= hwdep_write,311		.release	= hwdep_release,312		.poll		= hwdep_poll,313		.ioctl		= hwdep_ioctl,314		.ioctl_compat	= hwdep_compat_ioctl,315	};316	struct snd_hwdep *hwdep;317	int err;318 319	err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);320	if (err < 0)321		goto end;322	strcpy(hwdep->name, "Fireworks");323	hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;324	hwdep->ops = ops;325	hwdep->private_data = efw;326	hwdep->exclusive = true;327end:328	return err;329}330 331