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