1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * tascam-hwdep.c - a part of driver for TASCAM FireWire series
4 *
5 * Copyright (c) 2015 Takashi Sakamoto
6 */
7
8/*
9 * This codes give three functionality.
10 *
11 * 1.get firewire node information
12 * 2.get notification about starting/stopping stream
13 * 3.lock/unlock stream
14 */
15
16#include "tascam.h"
17
18static long tscm_hwdep_read_locked(struct snd_tscm *tscm, char __user *buf,
19				   long count, loff_t *offset)
20	__releases(&tscm->lock)
21{
22	struct snd_firewire_event_lock_status event = {
23		.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,
24	};
25
26	event.status = (tscm->dev_lock_count > 0);
27	tscm->dev_lock_changed = false;
28	count = min_t(long, count, sizeof(event));
29
30	spin_unlock_irq(&tscm->lock);
31
32	if (copy_to_user(buf, &event, count))
33		return -EFAULT;
34
35	return count;
36}
37
38static long tscm_hwdep_read_queue(struct snd_tscm *tscm, char __user *buf,
39				  long remained, loff_t *offset)
40	__releases(&tscm->lock)
41{
42	char __user *pos = buf;
43	unsigned int type = SNDRV_FIREWIRE_EVENT_TASCAM_CONTROL;
44	struct snd_firewire_tascam_change *entries = tscm->queue;
45	long count;
46
47	// At least, one control event can be copied.
48	if (remained < sizeof(type) + sizeof(*entries)) {
49		spin_unlock_irq(&tscm->lock);
50		return -EINVAL;
51	}
52
53	// Copy the type field later.
54	count = sizeof(type);
55	remained -= sizeof(type);
56	pos += sizeof(type);
57
58	while (true) {
59		unsigned int head_pos;
60		unsigned int tail_pos;
61		unsigned int length;
62
63		if (tscm->pull_pos == tscm->push_pos)
64			break;
65		else if (tscm->pull_pos < tscm->push_pos)
66			tail_pos = tscm->push_pos;
67		else
68			tail_pos = SND_TSCM_QUEUE_COUNT;
69		head_pos = tscm->pull_pos;
70
71		length = (tail_pos - head_pos) * sizeof(*entries);
72		if (remained < length)
73			length = rounddown(remained, sizeof(*entries));
74		if (length == 0)
75			break;
76
77		spin_unlock_irq(&tscm->lock);
78		if (copy_to_user(pos, &entries[head_pos], length))
79			return -EFAULT;
80
81		spin_lock_irq(&tscm->lock);
82
83		tscm->pull_pos = tail_pos % SND_TSCM_QUEUE_COUNT;
84
85		count += length;
86		remained -= length;
87		pos += length;
88	}
89
90	spin_unlock_irq(&tscm->lock);
91
92	if (copy_to_user(buf, &type, sizeof(type)))
93		return -EFAULT;
94
95	return count;
96}
97
98static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
99		       loff_t *offset)
100{
101	struct snd_tscm *tscm = hwdep->private_data;
102	DEFINE_WAIT(wait);
103
104	spin_lock_irq(&tscm->lock);
105
106	while (!tscm->dev_lock_changed && tscm->push_pos == tscm->pull_pos) {
107		prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
108		spin_unlock_irq(&tscm->lock);
109		schedule();
110		finish_wait(&tscm->hwdep_wait, &wait);
111		if (signal_pending(current))
112			return -ERESTARTSYS;
113		spin_lock_irq(&tscm->lock);
114	}
115
116	// NOTE: The acquired lock should be released in callee side.
117	if (tscm->dev_lock_changed) {
118		count = tscm_hwdep_read_locked(tscm, buf, count, offset);
119	} else if (tscm->push_pos != tscm->pull_pos) {
120		count = tscm_hwdep_read_queue(tscm, buf, count, offset);
121	} else {
122		spin_unlock_irq(&tscm->lock);
123		count = 0;
124	}
125
126	return count;
127}
128
129static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
130			       poll_table *wait)
131{
132	struct snd_tscm *tscm = hwdep->private_data;
133	__poll_t events;
134
135	poll_wait(file, &tscm->hwdep_wait, wait);
136
137	spin_lock_irq(&tscm->lock);
138	if (tscm->dev_lock_changed || tscm->push_pos != tscm->pull_pos)
139		events = EPOLLIN | EPOLLRDNORM;
140	else
141		events = 0;
142	spin_unlock_irq(&tscm->lock);
143
144	return events;
145}
146
147static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg)
148{
149	struct fw_device *dev = fw_parent_device(tscm->unit);
150	struct snd_firewire_get_info info;
151
152	memset(&info, 0, sizeof(info));
153	info.type = SNDRV_FIREWIRE_TYPE_TASCAM;
154	info.card = dev->card->index;
155	*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
156	*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
157	strscpy(info.device_name, dev_name(&dev->device),
158		sizeof(info.device_name));
159
160	if (copy_to_user(arg, &info, sizeof(info)))
161		return -EFAULT;
162
163	return 0;
164}
165
166static int hwdep_lock(struct snd_tscm *tscm)
167{
168	int err;
169
170	spin_lock_irq(&tscm->lock);
171
172	if (tscm->dev_lock_count == 0) {
173		tscm->dev_lock_count = -1;
174		err = 0;
175	} else {
176		err = -EBUSY;
177	}
178
179	spin_unlock_irq(&tscm->lock);
180
181	return err;
182}
183
184static int hwdep_unlock(struct snd_tscm *tscm)
185{
186	int err;
187
188	spin_lock_irq(&tscm->lock);
189
190	if (tscm->dev_lock_count == -1) {
191		tscm->dev_lock_count = 0;
192		err = 0;
193	} else {
194		err = -EBADFD;
195	}
196
197	spin_unlock_irq(&tscm->lock);
198
199	return err;
200}
201
202static int tscm_hwdep_state(struct snd_tscm *tscm, void __user *arg)
203{
204	if (copy_to_user(arg, tscm->state, sizeof(tscm->state)))
205		return -EFAULT;
206
207	return 0;
208}
209
210static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
211{
212	struct snd_tscm *tscm = hwdep->private_data;
213
214	spin_lock_irq(&tscm->lock);
215	if (tscm->dev_lock_count == -1)
216		tscm->dev_lock_count = 0;
217	spin_unlock_irq(&tscm->lock);
218
219	return 0;
220}
221
222static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
223	    unsigned int cmd, unsigned long arg)
224{
225	struct snd_tscm *tscm = hwdep->private_data;
226
227	switch (cmd) {
228	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
229		return hwdep_get_info(tscm, (void __user *)arg);
230	case SNDRV_FIREWIRE_IOCTL_LOCK:
231		return hwdep_lock(tscm);
232	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
233		return hwdep_unlock(tscm);
234	case SNDRV_FIREWIRE_IOCTL_TASCAM_STATE:
235		return tscm_hwdep_state(tscm, (void __user *)arg);
236	default:
237		return -ENOIOCTLCMD;
238	}
239}
240
241#ifdef CONFIG_COMPAT
242static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
243			      unsigned int cmd, unsigned long arg)
244{
245	return hwdep_ioctl(hwdep, file, cmd,
246			   (unsigned long)compat_ptr(arg));
247}
248#else
249#define hwdep_compat_ioctl NULL
250#endif
251
252int snd_tscm_create_hwdep_device(struct snd_tscm *tscm)
253{
254	static const struct snd_hwdep_ops ops = {
255		.read		= hwdep_read,
256		.release	= hwdep_release,
257		.poll		= hwdep_poll,
258		.ioctl		= hwdep_ioctl,
259		.ioctl_compat	= hwdep_compat_ioctl,
260	};
261	struct snd_hwdep *hwdep;
262	int err;
263
264	err = snd_hwdep_new(tscm->card, "Tascam", 0, &hwdep);
265	if (err < 0)
266		return err;
267
268	strcpy(hwdep->name, "Tascam");
269	hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM;
270	hwdep->ops = ops;
271	hwdep->private_data = tscm;
272	hwdep->exclusive = true;
273
274	tscm->hwdep = hwdep;
275
276	return err;
277}
278