File Index
Files and directories
Sign in
FILE

tascam-hwdep.c

File preview with syntax highlighting.

linux-6.19.14/sound/firewire/tascam/tascam-hwdep.c C
  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
134	poll_wait(file, &tscm->hwdep_wait, wait);
135
136	guard(spinlock_irq)(&tscm->lock);
137	if (tscm->dev_lock_changed || tscm->push_pos != tscm->pull_pos)
138		return EPOLLIN | EPOLLRDNORM;
139	else
140		return 0;
141}
142
143static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg)
144{
145	struct fw_device *dev = fw_parent_device(tscm->unit);
146	struct snd_firewire_get_info info;
147
148	memset(&info, 0, sizeof(info));
149	info.type = SNDRV_FIREWIRE_TYPE_TASCAM;
150	info.card = dev->card->index;
151	*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
152	*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
153	strscpy(info.device_name, dev_name(&dev->device),
154		sizeof(info.device_name));
155
156	if (copy_to_user(arg, &info, sizeof(info)))
157		return -EFAULT;
158
159	return 0;
160}
161
162static int hwdep_lock(struct snd_tscm *tscm)
163{
164	guard(spinlock_irq)(&tscm->lock);
165
166	if (tscm->dev_lock_count == 0) {
167		tscm->dev_lock_count = -1;
168		return 0;
169	} else {
170		return -EBUSY;
171	}
172}
173
174static int hwdep_unlock(struct snd_tscm *tscm)
175{
176	guard(spinlock_irq)(&tscm->lock);
177
178	if (tscm->dev_lock_count == -1) {
179		tscm->dev_lock_count = 0;
180		return 0;
181	} else {
182		return -EBADFD;
183	}
184}
185
186static int tscm_hwdep_state(struct snd_tscm *tscm, void __user *arg)
187{
188	if (copy_to_user(arg, tscm->state, sizeof(tscm->state)))
189		return -EFAULT;
190
191	return 0;
192}
193
194static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
195{
196	struct snd_tscm *tscm = hwdep->private_data;
197
198	guard(spinlock_irq)(&tscm->lock);
199	if (tscm->dev_lock_count == -1)
200		tscm->dev_lock_count = 0;
201
202	return 0;
203}
204
205static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
206	    unsigned int cmd, unsigned long arg)
207{
208	struct snd_tscm *tscm = hwdep->private_data;
209
210	switch (cmd) {
211	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
212		return hwdep_get_info(tscm, (void __user *)arg);
213	case SNDRV_FIREWIRE_IOCTL_LOCK:
214		return hwdep_lock(tscm);
215	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
216		return hwdep_unlock(tscm);
217	case SNDRV_FIREWIRE_IOCTL_TASCAM_STATE:
218		return tscm_hwdep_state(tscm, (void __user *)arg);
219	default:
220		return -ENOIOCTLCMD;
221	}
222}
223
224#ifdef CONFIG_COMPAT
225static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
226			      unsigned int cmd, unsigned long arg)
227{
228	return hwdep_ioctl(hwdep, file, cmd,
229			   (unsigned long)compat_ptr(arg));
230}
231#else
232#define hwdep_compat_ioctl NULL
233#endif
234
235int snd_tscm_create_hwdep_device(struct snd_tscm *tscm)
236{
237	static const struct snd_hwdep_ops ops = {
238		.read		= hwdep_read,
239		.release	= hwdep_release,
240		.poll		= hwdep_poll,
241		.ioctl		= hwdep_ioctl,
242		.ioctl_compat	= hwdep_compat_ioctl,
243	};
244	struct snd_hwdep *hwdep;
245	int err;
246
247	err = snd_hwdep_new(tscm->card, "Tascam", 0, &hwdep);
248	if (err < 0)
249		return err;
250
251	strscpy(hwdep->name, "Tascam");
252	hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM;
253	hwdep->ops = ops;
254	hwdep->private_data = tscm;
255	hwdep->exclusive = true;
256
257	tscm->hwdep = hwdep;
258
259	return err;
260}
~/ Read-only file preview.