xref: /linux/sound/firewire/fireworks/fireworks_hwdep.c (revision 05a54fa773284d1a7923cdfdd8f0c8dabb98bd26)
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 
21 static long
22 hwdep_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 
98 static long
99 hwdep_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 	scoped_guard(spinlock_irq, &efw->lock) {
107 		event.lock_status.status = (efw->dev_lock_count > 0);
108 		efw->dev_lock_changed = false;
109 	}
110 
111 	count = min_t(long, count, sizeof(event.lock_status));
112 
113 	if (copy_to_user(buf, &event, count))
114 		return -EFAULT;
115 
116 	return count;
117 }
118 
119 static long
120 hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
121 	   loff_t *offset)
122 {
123 	struct snd_efw *efw = hwdep->private_data;
124 	DEFINE_WAIT(wait);
125 	bool dev_lock_changed;
126 	bool queued;
127 
128 	spin_lock_irq(&efw->lock);
129 
130 	dev_lock_changed = efw->dev_lock_changed;
131 	queued = efw->push_ptr != efw->pull_ptr;
132 
133 	while (!dev_lock_changed && !queued) {
134 		prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
135 		spin_unlock_irq(&efw->lock);
136 		schedule();
137 		finish_wait(&efw->hwdep_wait, &wait);
138 		if (signal_pending(current))
139 			return -ERESTARTSYS;
140 		spin_lock_irq(&efw->lock);
141 		dev_lock_changed = efw->dev_lock_changed;
142 		queued = efw->push_ptr != efw->pull_ptr;
143 	}
144 
145 	spin_unlock_irq(&efw->lock);
146 
147 	if (dev_lock_changed)
148 		count = hwdep_read_locked(efw, buf, count, offset);
149 	else if (queued)
150 		count = hwdep_read_resp_buf(efw, buf, count, offset);
151 
152 	return count;
153 }
154 
155 static long
156 hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
157 	    loff_t *offset)
158 {
159 	struct snd_efw *efw = hwdep->private_data;
160 	u32 seqnum;
161 	u8 *buf;
162 
163 	if (count < sizeof(struct snd_efw_transaction) ||
164 	    SND_EFW_RESPONSE_MAXIMUM_BYTES < count)
165 		return -EINVAL;
166 
167 	buf = memdup_user(data, count);
168 	if (IS_ERR(buf))
169 		return PTR_ERR(buf);
170 
171 	/* check seqnum is not for kernel-land */
172 	seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum);
173 	if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) {
174 		count = -EINVAL;
175 		goto end;
176 	}
177 
178 	if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0)
179 		count = -EIO;
180 end:
181 	kfree(buf);
182 	return count;
183 }
184 
185 static __poll_t
186 hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
187 {
188 	struct snd_efw *efw = hwdep->private_data;
189 	__poll_t events;
190 
191 	poll_wait(file, &efw->hwdep_wait, wait);
192 
193 	guard(spinlock_irq)(&efw->lock);
194 	if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr)
195 		events = EPOLLIN | EPOLLRDNORM;
196 	else
197 		events = 0;
198 	return events | EPOLLOUT;
199 }
200 
201 static int
202 hwdep_get_info(struct snd_efw *efw, void __user *arg)
203 {
204 	struct fw_device *dev = fw_parent_device(efw->unit);
205 	struct snd_firewire_get_info info;
206 
207 	memset(&info, 0, sizeof(info));
208 	info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS;
209 	info.card = dev->card->index;
210 	*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
211 	*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
212 	strscpy(info.device_name, dev_name(&dev->device),
213 		sizeof(info.device_name));
214 
215 	if (copy_to_user(arg, &info, sizeof(info)))
216 		return -EFAULT;
217 
218 	return 0;
219 }
220 
221 static int
222 hwdep_lock(struct snd_efw *efw)
223 {
224 	guard(spinlock_irq)(&efw->lock);
225 
226 	if (efw->dev_lock_count == 0) {
227 		efw->dev_lock_count = -1;
228 		return 0;
229 	} else {
230 		return -EBUSY;
231 	}
232 }
233 
234 static int
235 hwdep_unlock(struct snd_efw *efw)
236 {
237 	guard(spinlock_irq)(&efw->lock);
238 
239 	if (efw->dev_lock_count == -1) {
240 		efw->dev_lock_count = 0;
241 		return 0;
242 	} else {
243 		return -EBADFD;
244 	}
245 }
246 
247 static int
248 hwdep_release(struct snd_hwdep *hwdep, struct file *file)
249 {
250 	struct snd_efw *efw = hwdep->private_data;
251 
252 	guard(spinlock_irq)(&efw->lock);
253 	if (efw->dev_lock_count == -1)
254 		efw->dev_lock_count = 0;
255 
256 	return 0;
257 }
258 
259 static int
260 hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
261 	    unsigned int cmd, unsigned long arg)
262 {
263 	struct snd_efw *efw = hwdep->private_data;
264 
265 	switch (cmd) {
266 	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
267 		return hwdep_get_info(efw, (void __user *)arg);
268 	case SNDRV_FIREWIRE_IOCTL_LOCK:
269 		return hwdep_lock(efw);
270 	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
271 		return hwdep_unlock(efw);
272 	default:
273 		return -ENOIOCTLCMD;
274 	}
275 }
276 
277 #ifdef CONFIG_COMPAT
278 static int
279 hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
280 		   unsigned int cmd, unsigned long arg)
281 {
282 	return hwdep_ioctl(hwdep, file, cmd,
283 			   (unsigned long)compat_ptr(arg));
284 }
285 #else
286 #define hwdep_compat_ioctl NULL
287 #endif
288 
289 int snd_efw_create_hwdep_device(struct snd_efw *efw)
290 {
291 	static const struct snd_hwdep_ops ops = {
292 		.read		= hwdep_read,
293 		.write		= hwdep_write,
294 		.release	= hwdep_release,
295 		.poll		= hwdep_poll,
296 		.ioctl		= hwdep_ioctl,
297 		.ioctl_compat	= hwdep_compat_ioctl,
298 	};
299 	struct snd_hwdep *hwdep;
300 	int err;
301 
302 	err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);
303 	if (err < 0)
304 		goto end;
305 	strscpy(hwdep->name, "Fireworks");
306 	hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;
307 	hwdep->ops = ops;
308 	hwdep->private_data = efw;
309 	hwdep->exclusive = true;
310 end:
311 	return err;
312 }
313 
314