Commit | Line | Data |
---|---|---|
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 |
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 | ||
a8c0d132 TS |
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 | ||
e5e0c3dd TS |
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); | |
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 | 127 | static __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 | ||
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 | ||
90e8ac5c TS |
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 | ||
e5e0c3dd TS |
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); | |
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 | |
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 | ||
e5e0c3dd TS |
250 | int 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 | } |