Merge branch 'hugepage-fallbacks' (hugepatch patches from David Rientjes)
[linux-2.6-block.git] / sound / firewire / tascam / tascam-hwdep.c
CommitLineData
da607e19 1// SPDX-License-Identifier: GPL-2.0-only
e5e0c3dd
TS
2/*
3 * tascam-hwdep.c - a part of driver for TASCAM FireWire series
4 *
5 * Copyright (c) 2015 Takashi Sakamoto
e5e0c3dd
TS
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
afb8e2da
TS
18static 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
a8c0d132
TS
37static 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
e5e0c3dd
TS
96static 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);
e5e0c3dd
TS
101
102 spin_lock_irq(&tscm->lock);
103
a8c0d132 104 while (!tscm->dev_lock_changed && tscm->push_pos == tscm->pull_pos) {
e5e0c3dd
TS
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
afb8e2da
TS
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);
a8c0d132
TS
117 } else if (tscm->push_pos != tscm->pull_pos) {
118 count = tscm_hwdep_read_queue(tscm, buf, count, offset);
afb8e2da
TS
119 } else {
120 spin_unlock_irq(&tscm->lock);
121 count = 0;
122 }
04b2d9c9 123
e5e0c3dd
TS
124 return count;
125}
126
680ef72a 127static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
e5e0c3dd
TS
128 poll_table *wait)
129{
130 struct snd_tscm *tscm = hwdep->private_data;
680ef72a 131 __poll_t events;
e5e0c3dd
TS
132
133 poll_wait(file, &tscm->hwdep_wait, wait);
134
135 spin_lock_irq(&tscm->lock);
a8c0d132 136 if (tscm->dev_lock_changed || tscm->push_pos != tscm->pull_pos)
a9a08845 137 events = EPOLLIN | EPOLLRDNORM;
e5e0c3dd
TS
138 else
139 events = 0;
140 spin_unlock_irq(&tscm->lock);
141
142 return events;
143}
144
145static 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
164static 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
182static 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
90e8ac5c
TS
200static 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
e5e0c3dd
TS
208static 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
220static 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);
90e8ac5c
TS
232 case SNDRV_FIREWIRE_IOCTL_TASCAM_STATE:
233 return tscm_hwdep_state(tscm, (void __user *)arg);
e5e0c3dd
TS
234 default:
235 return -ENOIOCTLCMD;
236 }
237}
238
239#ifdef CONFIG_COMPAT
240static 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
e5e0c3dd
TS
250int snd_tscm_create_hwdep_device(struct snd_tscm *tscm)
251{
92128236
TS
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 };
e5e0c3dd
TS
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;
92128236 268 hwdep->ops = ops;
e5e0c3dd
TS
269 hwdep->private_data = tscm;
270 hwdep->exclusive = true;
271
d7167422
TS
272 tscm->hwdep = hwdep;
273
e5e0c3dd
TS
274 return err;
275}