1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * fireworks_hwdep.c - a part of driver for Fireworks based devices
4 *
5 * Copyright (c) 2013-2014 Takashi Sakamoto
6 */
7
8/*
9 * This codes have five functionalities.
10 *
11 * 1.get information about firewire node
12 * 2.get notification about starting/stopping stream
13 * 3.lock/unlock streaming
14 * 4.transmit command of EFW transaction
15 * 5.receive response of EFW transaction
16 *
17 */
18
19#include "fireworks.h"
20
21static long
22hwdep_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 user
46	 * space, it picks up current position in buffer and can read the same
47	 * 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 the
85	 * last position for each task is different depending on the length of
86	 * given buffer. Here, for simplicity, a position of buffer is set by
87	 * the latest task. It's better for a listening application to allow one
88	 * thread to read from the buffer. Unless, each task can read different
89	 * 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 long
99hwdep_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 long
122hwdep_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 long
158hwdep_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_t
188hwdep_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	else
199		events = 0;
200	spin_unlock_irq(&efw->lock);
201
202	return events | EPOLLOUT;
203}
204
205static int
206hwdep_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 int
226hwdep_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 int
245hwdep_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 int
264hwdep_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 int
277hwdep_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_COMPAT
295static int
296hwdep_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#else
303#define hwdep_compat_ioctl NULL
304#endif
305
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