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
tscm_hwdep_read_locked(struct snd_tscm * tscm,char __user * buf,long count,loff_t * offset)18 static 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
tscm_hwdep_read_queue(struct snd_tscm * tscm,char __user * buf,long remained,loff_t * offset)38 static 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
hwdep_read(struct snd_hwdep * hwdep,char __user * buf,long count,loff_t * offset)98 static 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
hwdep_poll(struct snd_hwdep * hwdep,struct file * file,poll_table * wait)129 static __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
hwdep_get_info(struct snd_tscm * tscm,void __user * arg)147 static 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
hwdep_lock(struct snd_tscm * tscm)166 static 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
hwdep_unlock(struct snd_tscm * tscm)184 static 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
tscm_hwdep_state(struct snd_tscm * tscm,void __user * arg)202 static 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
hwdep_release(struct snd_hwdep * hwdep,struct file * file)210 static 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
hwdep_ioctl(struct snd_hwdep * hwdep,struct file * file,unsigned int cmd,unsigned long arg)222 static 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
hwdep_compat_ioctl(struct snd_hwdep * hwdep,struct file * file,unsigned int cmd,unsigned long arg)242 static 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
snd_tscm_create_hwdep_device(struct snd_tscm * tscm)252 int 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