Commit | Line | Data |
---|---|---|
594ddced TS |
1 | /* |
2 | * fireworks_hwdep.c - a part of driver for Fireworks based devices | |
3 | * | |
4 | * Copyright (c) 2013-2014 Takashi Sakamoto | |
5 | * | |
6 | * Licensed under the terms of the GNU General Public License, version 2. | |
7 | */ | |
8 | ||
9 | /* | |
555e8a8f | 10 | * This codes have five functionalities. |
594ddced TS |
11 | * |
12 | * 1.get information about firewire node | |
13 | * 2.get notification about starting/stopping stream | |
14 | * 3.lock/unlock streaming | |
555e8a8f TS |
15 | * 4.transmit command of EFW transaction |
16 | * 5.receive response of EFW transaction | |
17 | * | |
594ddced TS |
18 | */ |
19 | ||
20 | #include "fireworks.h" | |
21 | ||
22 | static long | |
555e8a8f TS |
23 | hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained, |
24 | loff_t *offset) | |
25 | { | |
26 | unsigned int length, till_end, type; | |
27 | struct snd_efw_transaction *t; | |
6b1ca4bc | 28 | u8 *pull_ptr; |
555e8a8f TS |
29 | long count = 0; |
30 | ||
31 | if (remained < sizeof(type) + sizeof(struct snd_efw_transaction)) | |
32 | return -ENOSPC; | |
33 | ||
34 | /* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */ | |
35 | type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE; | |
36 | if (copy_to_user(buf, &type, sizeof(type))) | |
37 | return -EFAULT; | |
38 | remained -= sizeof(type); | |
39 | buf += sizeof(type); | |
40 | ||
41 | /* write into buffer as many responses as possible */ | |
6b1ca4bc TS |
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); | |
555e8a8f TS |
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 - | |
6b1ca4bc | 62 | (unsigned int)(pull_ptr - efw->resp_buf); |
555e8a8f TS |
63 | till_end = min_t(unsigned int, length, till_end); |
64 | ||
6b1ca4bc TS |
65 | spin_unlock_irq(&efw->lock); |
66 | ||
67 | if (copy_to_user(buf, pull_ptr, till_end)) | |
555e8a8f TS |
68 | return -EFAULT; |
69 | ||
6b1ca4bc TS |
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; | |
555e8a8f TS |
75 | |
76 | length -= till_end; | |
77 | buf += till_end; | |
78 | count += till_end; | |
79 | remained -= till_end; | |
80 | } | |
555e8a8f TS |
81 | } |
82 | ||
6b1ca4bc TS |
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 | ||
555e8a8f TS |
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 | { | |
6b1ca4bc TS |
102 | union snd_firewire_event event = { |
103 | .lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS, | |
104 | }; | |
555e8a8f | 105 | |
6b1ca4bc | 106 | spin_lock_irq(&efw->lock); |
555e8a8f | 107 | |
555e8a8f TS |
108 | event.lock_status.status = (efw->dev_lock_count > 0); |
109 | efw->dev_lock_changed = false; | |
110 | ||
6b1ca4bc TS |
111 | spin_unlock_irq(&efw->lock); |
112 | ||
555e8a8f TS |
113 | count = min_t(long, count, sizeof(event.lock_status)); |
114 | ||
115 | if (copy_to_user(buf, &event, count)) | |
116 | return -EFAULT; | |
117 | ||
118 | return count; | |
119 | } | |
120 | ||
121 | static long | |
122 | hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, | |
594ddced TS |
123 | loff_t *offset) |
124 | { | |
125 | struct snd_efw *efw = hwdep->private_data; | |
126 | DEFINE_WAIT(wait); | |
6b1ca4bc TS |
127 | bool dev_lock_changed; |
128 | bool queued; | |
594ddced TS |
129 | |
130 | spin_lock_irq(&efw->lock); | |
131 | ||
6b1ca4bc TS |
132 | dev_lock_changed = efw->dev_lock_changed; |
133 | queued = efw->push_ptr != efw->pull_ptr; | |
134 | ||
135 | while (!dev_lock_changed && !queued) { | |
594ddced TS |
136 | prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); |
137 | spin_unlock_irq(&efw->lock); | |
138 | schedule(); | |
139 | finish_wait(&efw->hwdep_wait, &wait); | |
140 | if (signal_pending(current)) | |
141 | return -ERESTARTSYS; | |
142 | spin_lock_irq(&efw->lock); | |
6b1ca4bc TS |
143 | dev_lock_changed = efw->dev_lock_changed; |
144 | queued = efw->push_ptr != efw->pull_ptr; | |
594ddced TS |
145 | } |
146 | ||
6b1ca4bc TS |
147 | spin_unlock_irq(&efw->lock); |
148 | ||
149 | if (dev_lock_changed) | |
555e8a8f | 150 | count = hwdep_read_locked(efw, buf, count, offset); |
6b1ca4bc | 151 | else if (queued) |
555e8a8f | 152 | count = hwdep_read_resp_buf(efw, buf, count, offset); |
594ddced | 153 | |
555e8a8f TS |
154 | return count; |
155 | } | |
594ddced | 156 | |
555e8a8f TS |
157 | static long |
158 | hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count, | |
159 | loff_t *offset) | |
160 | { | |
161 | struct snd_efw *efw = hwdep->private_data; | |
162 | u32 seqnum; | |
163 | u8 *buf; | |
164 | ||
165 | if (count < sizeof(struct snd_efw_transaction) || | |
166 | SND_EFW_RESPONSE_MAXIMUM_BYTES < count) | |
167 | return -EINVAL; | |
168 | ||
169 | buf = memdup_user(data, count); | |
170 | if (IS_ERR(buf)) | |
ba06b2cb | 171 | return PTR_ERR(buf); |
555e8a8f TS |
172 | |
173 | /* check seqnum is not for kernel-land */ | |
174 | seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum); | |
175 | if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) { | |
176 | count = -EINVAL; | |
177 | goto end; | |
178 | } | |
179 | ||
180 | if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0) | |
181 | count = -EIO; | |
182 | end: | |
183 | kfree(buf); | |
594ddced TS |
184 | return count; |
185 | } | |
186 | ||
187 | static unsigned int | |
188 | hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) | |
189 | { | |
190 | struct snd_efw *efw = hwdep->private_data; | |
191 | unsigned int events; | |
192 | ||
193 | poll_wait(file, &efw->hwdep_wait, wait); | |
194 | ||
195 | spin_lock_irq(&efw->lock); | |
6b1ca4bc | 196 | if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr) |
594ddced TS |
197 | events = POLLIN | POLLRDNORM; |
198 | else | |
199 | events = 0; | |
200 | spin_unlock_irq(&efw->lock); | |
201 | ||
555e8a8f | 202 | return events | POLLOUT; |
594ddced TS |
203 | } |
204 | ||
205 | static int | |
206 | hwdep_get_info(struct snd_efw *efw, void __user *arg) | |
207 | { | |
208 | struct fw_device *dev = fw_parent_device(efw->unit); | |
209 | struct snd_firewire_get_info info; | |
210 | ||
211 | memset(&info, 0, sizeof(info)); | |
212 | info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS; | |
213 | info.card = dev->card->index; | |
214 | *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); | |
215 | *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); | |
216 | strlcpy(info.device_name, dev_name(&dev->device), | |
217 | sizeof(info.device_name)); | |
218 | ||
219 | if (copy_to_user(arg, &info, sizeof(info))) | |
220 | return -EFAULT; | |
221 | ||
222 | return 0; | |
223 | } | |
224 | ||
225 | static int | |
226 | hwdep_lock(struct snd_efw *efw) | |
227 | { | |
228 | int err; | |
229 | ||
230 | spin_lock_irq(&efw->lock); | |
231 | ||
232 | if (efw->dev_lock_count == 0) { | |
233 | efw->dev_lock_count = -1; | |
234 | err = 0; | |
235 | } else { | |
236 | err = -EBUSY; | |
237 | } | |
238 | ||
239 | spin_unlock_irq(&efw->lock); | |
240 | ||
241 | return err; | |
242 | } | |
243 | ||
244 | static int | |
245 | hwdep_unlock(struct snd_efw *efw) | |
246 | { | |
247 | int err; | |
248 | ||
249 | spin_lock_irq(&efw->lock); | |
250 | ||
251 | if (efw->dev_lock_count == -1) { | |
252 | efw->dev_lock_count = 0; | |
253 | err = 0; | |
254 | } else { | |
255 | err = -EBADFD; | |
256 | } | |
257 | ||
258 | spin_unlock_irq(&efw->lock); | |
259 | ||
260 | return err; | |
261 | } | |
262 | ||
263 | static int | |
264 | hwdep_release(struct snd_hwdep *hwdep, struct file *file) | |
265 | { | |
266 | struct snd_efw *efw = hwdep->private_data; | |
267 | ||
268 | spin_lock_irq(&efw->lock); | |
269 | if (efw->dev_lock_count == -1) | |
270 | efw->dev_lock_count = 0; | |
271 | spin_unlock_irq(&efw->lock); | |
272 | ||
273 | return 0; | |
274 | } | |
275 | ||
276 | static int | |
277 | hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, | |
278 | unsigned int cmd, unsigned long arg) | |
279 | { | |
280 | struct snd_efw *efw = hwdep->private_data; | |
281 | ||
282 | switch (cmd) { | |
283 | case SNDRV_FIREWIRE_IOCTL_GET_INFO: | |
284 | return hwdep_get_info(efw, (void __user *)arg); | |
285 | case SNDRV_FIREWIRE_IOCTL_LOCK: | |
286 | return hwdep_lock(efw); | |
287 | case SNDRV_FIREWIRE_IOCTL_UNLOCK: | |
288 | return hwdep_unlock(efw); | |
289 | default: | |
290 | return -ENOIOCTLCMD; | |
291 | } | |
292 | } | |
293 | ||
294 | #ifdef CONFIG_COMPAT | |
295 | static int | |
296 | hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, | |
297 | unsigned int cmd, unsigned long arg) | |
298 | { | |
299 | return hwdep_ioctl(hwdep, file, cmd, | |
300 | (unsigned long)compat_ptr(arg)); | |
301 | } | |
302 | #else | |
303 | #define hwdep_compat_ioctl NULL | |
304 | #endif | |
305 | ||
594ddced TS |
306 | int snd_efw_create_hwdep_device(struct snd_efw *efw) |
307 | { | |
7cdc887a TS |
308 | static const struct snd_hwdep_ops ops = { |
309 | .read = hwdep_read, | |
310 | .write = hwdep_write, | |
311 | .release = hwdep_release, | |
312 | .poll = hwdep_poll, | |
313 | .ioctl = hwdep_ioctl, | |
314 | .ioctl_compat = hwdep_compat_ioctl, | |
315 | }; | |
594ddced TS |
316 | struct snd_hwdep *hwdep; |
317 | int err; | |
318 | ||
319 | err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep); | |
320 | if (err < 0) | |
321 | goto end; | |
322 | strcpy(hwdep->name, "Fireworks"); | |
323 | hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS; | |
7cdc887a | 324 | hwdep->ops = ops; |
594ddced TS |
325 | hwdep->private_data = efw; |
326 | hwdep->exclusive = true; | |
327 | end: | |
328 | return err; | |
329 | } | |
330 |