18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * fireworks_hwdep.c - a part of driver for Fireworks based devices
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2013-2014 Takashi Sakamoto
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci/*
98c2ecf20Sopenharmony_ci * This codes have five functionalities.
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * 1.get information about firewire node
128c2ecf20Sopenharmony_ci * 2.get notification about starting/stopping stream
138c2ecf20Sopenharmony_ci * 3.lock/unlock streaming
148c2ecf20Sopenharmony_ci * 4.transmit command of EFW transaction
158c2ecf20Sopenharmony_ci * 5.receive response of EFW transaction
168c2ecf20Sopenharmony_ci *
178c2ecf20Sopenharmony_ci */
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include "fireworks.h"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistatic long
228c2ecf20Sopenharmony_cihwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
238c2ecf20Sopenharmony_ci		    loff_t *offset)
248c2ecf20Sopenharmony_ci{
258c2ecf20Sopenharmony_ci	unsigned int length, till_end, type;
268c2ecf20Sopenharmony_ci	struct snd_efw_transaction *t;
278c2ecf20Sopenharmony_ci	u8 *pull_ptr;
288c2ecf20Sopenharmony_ci	long count = 0;
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	if (remained < sizeof(type) + sizeof(struct snd_efw_transaction))
318c2ecf20Sopenharmony_ci		return -ENOSPC;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	/* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */
348c2ecf20Sopenharmony_ci	type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE;
358c2ecf20Sopenharmony_ci	if (copy_to_user(buf, &type, sizeof(type)))
368c2ecf20Sopenharmony_ci		return -EFAULT;
378c2ecf20Sopenharmony_ci	count += sizeof(type);
388c2ecf20Sopenharmony_ci	remained -= sizeof(type);
398c2ecf20Sopenharmony_ci	buf += sizeof(type);
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	/* write into buffer as many responses as possible */
428c2ecf20Sopenharmony_ci	spin_lock_irq(&efw->lock);
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	/*
458c2ecf20Sopenharmony_ci	 * When another task reaches here during this task's access to user
468c2ecf20Sopenharmony_ci	 * space, it picks up current position in buffer and can read the same
478c2ecf20Sopenharmony_ci	 * series of responses.
488c2ecf20Sopenharmony_ci	 */
498c2ecf20Sopenharmony_ci	pull_ptr = efw->pull_ptr;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	while (efw->push_ptr != pull_ptr) {
528c2ecf20Sopenharmony_ci		t = (struct snd_efw_transaction *)(pull_ptr);
538c2ecf20Sopenharmony_ci		length = be32_to_cpu(t->length) * sizeof(__be32);
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci		/* confirm enough space for this response */
568c2ecf20Sopenharmony_ci		if (remained < length)
578c2ecf20Sopenharmony_ci			break;
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci		/* copy from ring buffer to user buffer */
608c2ecf20Sopenharmony_ci		while (length > 0) {
618c2ecf20Sopenharmony_ci			till_end = snd_efw_resp_buf_size -
628c2ecf20Sopenharmony_ci				(unsigned int)(pull_ptr - efw->resp_buf);
638c2ecf20Sopenharmony_ci			till_end = min_t(unsigned int, length, till_end);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci			spin_unlock_irq(&efw->lock);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci			if (copy_to_user(buf, pull_ptr, till_end))
688c2ecf20Sopenharmony_ci				return -EFAULT;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci			spin_lock_irq(&efw->lock);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci			pull_ptr += till_end;
738c2ecf20Sopenharmony_ci			if (pull_ptr >= efw->resp_buf + snd_efw_resp_buf_size)
748c2ecf20Sopenharmony_ci				pull_ptr -= snd_efw_resp_buf_size;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci			length -= till_end;
778c2ecf20Sopenharmony_ci			buf += till_end;
788c2ecf20Sopenharmony_ci			count += till_end;
798c2ecf20Sopenharmony_ci			remained -= till_end;
808c2ecf20Sopenharmony_ci		}
818c2ecf20Sopenharmony_ci	}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	/*
848c2ecf20Sopenharmony_ci	 * All of tasks can read from the buffer nearly simultaneously, but the
858c2ecf20Sopenharmony_ci	 * last position for each task is different depending on the length of
868c2ecf20Sopenharmony_ci	 * given buffer. Here, for simplicity, a position of buffer is set by
878c2ecf20Sopenharmony_ci	 * the latest task. It's better for a listening application to allow one
888c2ecf20Sopenharmony_ci	 * thread to read from the buffer. Unless, each task can read different
898c2ecf20Sopenharmony_ci	 * sequence of responses depending on variation of buffer length.
908c2ecf20Sopenharmony_ci	 */
918c2ecf20Sopenharmony_ci	efw->pull_ptr = pull_ptr;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	spin_unlock_irq(&efw->lock);
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	return count;
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistatic long
998c2ecf20Sopenharmony_cihwdep_read_locked(struct snd_efw *efw, char __user *buf, long count,
1008c2ecf20Sopenharmony_ci		  loff_t *offset)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	union snd_firewire_event event = {
1038c2ecf20Sopenharmony_ci		.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,
1048c2ecf20Sopenharmony_ci	};
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	spin_lock_irq(&efw->lock);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	event.lock_status.status = (efw->dev_lock_count > 0);
1098c2ecf20Sopenharmony_ci	efw->dev_lock_changed = false;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	spin_unlock_irq(&efw->lock);
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	count = min_t(long, count, sizeof(event.lock_status));
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	if (copy_to_user(buf, &event, count))
1168c2ecf20Sopenharmony_ci		return -EFAULT;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	return count;
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic long
1228c2ecf20Sopenharmony_cihwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
1238c2ecf20Sopenharmony_ci	   loff_t *offset)
1248c2ecf20Sopenharmony_ci{
1258c2ecf20Sopenharmony_ci	struct snd_efw *efw = hwdep->private_data;
1268c2ecf20Sopenharmony_ci	DEFINE_WAIT(wait);
1278c2ecf20Sopenharmony_ci	bool dev_lock_changed;
1288c2ecf20Sopenharmony_ci	bool queued;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	spin_lock_irq(&efw->lock);
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	dev_lock_changed = efw->dev_lock_changed;
1338c2ecf20Sopenharmony_ci	queued = efw->push_ptr != efw->pull_ptr;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	while (!dev_lock_changed && !queued) {
1368c2ecf20Sopenharmony_ci		prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
1378c2ecf20Sopenharmony_ci		spin_unlock_irq(&efw->lock);
1388c2ecf20Sopenharmony_ci		schedule();
1398c2ecf20Sopenharmony_ci		finish_wait(&efw->hwdep_wait, &wait);
1408c2ecf20Sopenharmony_ci		if (signal_pending(current))
1418c2ecf20Sopenharmony_ci			return -ERESTARTSYS;
1428c2ecf20Sopenharmony_ci		spin_lock_irq(&efw->lock);
1438c2ecf20Sopenharmony_ci		dev_lock_changed = efw->dev_lock_changed;
1448c2ecf20Sopenharmony_ci		queued = efw->push_ptr != efw->pull_ptr;
1458c2ecf20Sopenharmony_ci	}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	spin_unlock_irq(&efw->lock);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	if (dev_lock_changed)
1508c2ecf20Sopenharmony_ci		count = hwdep_read_locked(efw, buf, count, offset);
1518c2ecf20Sopenharmony_ci	else if (queued)
1528c2ecf20Sopenharmony_ci		count = hwdep_read_resp_buf(efw, buf, count, offset);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	return count;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic long
1588c2ecf20Sopenharmony_cihwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
1598c2ecf20Sopenharmony_ci	    loff_t *offset)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	struct snd_efw *efw = hwdep->private_data;
1628c2ecf20Sopenharmony_ci	u32 seqnum;
1638c2ecf20Sopenharmony_ci	u8 *buf;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	if (count < sizeof(struct snd_efw_transaction) ||
1668c2ecf20Sopenharmony_ci	    SND_EFW_RESPONSE_MAXIMUM_BYTES < count)
1678c2ecf20Sopenharmony_ci		return -EINVAL;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	buf = memdup_user(data, count);
1708c2ecf20Sopenharmony_ci	if (IS_ERR(buf))
1718c2ecf20Sopenharmony_ci		return PTR_ERR(buf);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	/* check seqnum is not for kernel-land */
1748c2ecf20Sopenharmony_ci	seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum);
1758c2ecf20Sopenharmony_ci	if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) {
1768c2ecf20Sopenharmony_ci		count = -EINVAL;
1778c2ecf20Sopenharmony_ci		goto end;
1788c2ecf20Sopenharmony_ci	}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0)
1818c2ecf20Sopenharmony_ci		count = -EIO;
1828c2ecf20Sopenharmony_ciend:
1838c2ecf20Sopenharmony_ci	kfree(buf);
1848c2ecf20Sopenharmony_ci	return count;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic __poll_t
1888c2ecf20Sopenharmony_cihwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
1898c2ecf20Sopenharmony_ci{
1908c2ecf20Sopenharmony_ci	struct snd_efw *efw = hwdep->private_data;
1918c2ecf20Sopenharmony_ci	__poll_t events;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	poll_wait(file, &efw->hwdep_wait, wait);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	spin_lock_irq(&efw->lock);
1968c2ecf20Sopenharmony_ci	if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr)
1978c2ecf20Sopenharmony_ci		events = EPOLLIN | EPOLLRDNORM;
1988c2ecf20Sopenharmony_ci	else
1998c2ecf20Sopenharmony_ci		events = 0;
2008c2ecf20Sopenharmony_ci	spin_unlock_irq(&efw->lock);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	return events | EPOLLOUT;
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic int
2068c2ecf20Sopenharmony_cihwdep_get_info(struct snd_efw *efw, void __user *arg)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	struct fw_device *dev = fw_parent_device(efw->unit);
2098c2ecf20Sopenharmony_ci	struct snd_firewire_get_info info;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	memset(&info, 0, sizeof(info));
2128c2ecf20Sopenharmony_ci	info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS;
2138c2ecf20Sopenharmony_ci	info.card = dev->card->index;
2148c2ecf20Sopenharmony_ci	*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
2158c2ecf20Sopenharmony_ci	*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
2168c2ecf20Sopenharmony_ci	strlcpy(info.device_name, dev_name(&dev->device),
2178c2ecf20Sopenharmony_ci		sizeof(info.device_name));
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	if (copy_to_user(arg, &info, sizeof(info)))
2208c2ecf20Sopenharmony_ci		return -EFAULT;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	return 0;
2238c2ecf20Sopenharmony_ci}
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_cistatic int
2268c2ecf20Sopenharmony_cihwdep_lock(struct snd_efw *efw)
2278c2ecf20Sopenharmony_ci{
2288c2ecf20Sopenharmony_ci	int err;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	spin_lock_irq(&efw->lock);
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	if (efw->dev_lock_count == 0) {
2338c2ecf20Sopenharmony_ci		efw->dev_lock_count = -1;
2348c2ecf20Sopenharmony_ci		err = 0;
2358c2ecf20Sopenharmony_ci	} else {
2368c2ecf20Sopenharmony_ci		err = -EBUSY;
2378c2ecf20Sopenharmony_ci	}
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	spin_unlock_irq(&efw->lock);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	return err;
2428c2ecf20Sopenharmony_ci}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_cistatic int
2458c2ecf20Sopenharmony_cihwdep_unlock(struct snd_efw *efw)
2468c2ecf20Sopenharmony_ci{
2478c2ecf20Sopenharmony_ci	int err;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	spin_lock_irq(&efw->lock);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	if (efw->dev_lock_count == -1) {
2528c2ecf20Sopenharmony_ci		efw->dev_lock_count = 0;
2538c2ecf20Sopenharmony_ci		err = 0;
2548c2ecf20Sopenharmony_ci	} else {
2558c2ecf20Sopenharmony_ci		err = -EBADFD;
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	spin_unlock_irq(&efw->lock);
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	return err;
2618c2ecf20Sopenharmony_ci}
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_cistatic int
2648c2ecf20Sopenharmony_cihwdep_release(struct snd_hwdep *hwdep, struct file *file)
2658c2ecf20Sopenharmony_ci{
2668c2ecf20Sopenharmony_ci	struct snd_efw *efw = hwdep->private_data;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	spin_lock_irq(&efw->lock);
2698c2ecf20Sopenharmony_ci	if (efw->dev_lock_count == -1)
2708c2ecf20Sopenharmony_ci		efw->dev_lock_count = 0;
2718c2ecf20Sopenharmony_ci	spin_unlock_irq(&efw->lock);
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return 0;
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_cistatic int
2778c2ecf20Sopenharmony_cihwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
2788c2ecf20Sopenharmony_ci	    unsigned int cmd, unsigned long arg)
2798c2ecf20Sopenharmony_ci{
2808c2ecf20Sopenharmony_ci	struct snd_efw *efw = hwdep->private_data;
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	switch (cmd) {
2838c2ecf20Sopenharmony_ci	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
2848c2ecf20Sopenharmony_ci		return hwdep_get_info(efw, (void __user *)arg);
2858c2ecf20Sopenharmony_ci	case SNDRV_FIREWIRE_IOCTL_LOCK:
2868c2ecf20Sopenharmony_ci		return hwdep_lock(efw);
2878c2ecf20Sopenharmony_ci	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
2888c2ecf20Sopenharmony_ci		return hwdep_unlock(efw);
2898c2ecf20Sopenharmony_ci	default:
2908c2ecf20Sopenharmony_ci		return -ENOIOCTLCMD;
2918c2ecf20Sopenharmony_ci	}
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT
2958c2ecf20Sopenharmony_cistatic int
2968c2ecf20Sopenharmony_cihwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
2978c2ecf20Sopenharmony_ci		   unsigned int cmd, unsigned long arg)
2988c2ecf20Sopenharmony_ci{
2998c2ecf20Sopenharmony_ci	return hwdep_ioctl(hwdep, file, cmd,
3008c2ecf20Sopenharmony_ci			   (unsigned long)compat_ptr(arg));
3018c2ecf20Sopenharmony_ci}
3028c2ecf20Sopenharmony_ci#else
3038c2ecf20Sopenharmony_ci#define hwdep_compat_ioctl NULL
3048c2ecf20Sopenharmony_ci#endif
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ciint snd_efw_create_hwdep_device(struct snd_efw *efw)
3078c2ecf20Sopenharmony_ci{
3088c2ecf20Sopenharmony_ci	static const struct snd_hwdep_ops ops = {
3098c2ecf20Sopenharmony_ci		.read		= hwdep_read,
3108c2ecf20Sopenharmony_ci		.write		= hwdep_write,
3118c2ecf20Sopenharmony_ci		.release	= hwdep_release,
3128c2ecf20Sopenharmony_ci		.poll		= hwdep_poll,
3138c2ecf20Sopenharmony_ci		.ioctl		= hwdep_ioctl,
3148c2ecf20Sopenharmony_ci		.ioctl_compat	= hwdep_compat_ioctl,
3158c2ecf20Sopenharmony_ci	};
3168c2ecf20Sopenharmony_ci	struct snd_hwdep *hwdep;
3178c2ecf20Sopenharmony_ci	int err;
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);
3208c2ecf20Sopenharmony_ci	if (err < 0)
3218c2ecf20Sopenharmony_ci		goto end;
3228c2ecf20Sopenharmony_ci	strcpy(hwdep->name, "Fireworks");
3238c2ecf20Sopenharmony_ci	hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;
3248c2ecf20Sopenharmony_ci	hwdep->ops = ops;
3258c2ecf20Sopenharmony_ci	hwdep->private_data = efw;
3268c2ecf20Sopenharmony_ci	hwdep->exclusive = true;
3278c2ecf20Sopenharmony_ciend:
3288c2ecf20Sopenharmony_ci	return err;
3298c2ecf20Sopenharmony_ci}
3308c2ecf20Sopenharmony_ci
331