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; | |
28 | long count = 0; | |
29 | ||
30 | if (remained < sizeof(type) + sizeof(struct snd_efw_transaction)) | |
31 | return -ENOSPC; | |
32 | ||
33 | /* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */ | |
34 | type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE; | |
35 | if (copy_to_user(buf, &type, sizeof(type))) | |
36 | return -EFAULT; | |
37 | remained -= sizeof(type); | |
38 | buf += sizeof(type); | |
39 | ||
40 | /* write into buffer as many responses as possible */ | |
41 | while (efw->resp_queues > 0) { | |
42 | t = (struct snd_efw_transaction *)(efw->pull_ptr); | |
43 | length = be32_to_cpu(t->length) * sizeof(__be32); | |
44 | ||
45 | /* confirm enough space for this response */ | |
46 | if (remained < length) | |
47 | break; | |
48 | ||
49 | /* copy from ring buffer to user buffer */ | |
50 | while (length > 0) { | |
51 | till_end = snd_efw_resp_buf_size - | |
52 | (unsigned int)(efw->pull_ptr - efw->resp_buf); | |
53 | till_end = min_t(unsigned int, length, till_end); | |
54 | ||
55 | if (copy_to_user(buf, efw->pull_ptr, till_end)) | |
56 | return -EFAULT; | |
57 | ||
58 | efw->pull_ptr += till_end; | |
59 | if (efw->pull_ptr >= efw->resp_buf + | |
60 | snd_efw_resp_buf_size) | |
cf44a136 | 61 | efw->pull_ptr -= snd_efw_resp_buf_size; |
555e8a8f TS |
62 | |
63 | length -= till_end; | |
64 | buf += till_end; | |
65 | count += till_end; | |
66 | remained -= till_end; | |
67 | } | |
68 | ||
69 | efw->resp_queues--; | |
70 | } | |
71 | ||
72 | return count; | |
73 | } | |
74 | ||
75 | static long | |
76 | hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count, | |
77 | loff_t *offset) | |
78 | { | |
79 | union snd_firewire_event event; | |
80 | ||
81 | memset(&event, 0, sizeof(event)); | |
82 | ||
83 | event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; | |
84 | event.lock_status.status = (efw->dev_lock_count > 0); | |
85 | efw->dev_lock_changed = false; | |
86 | ||
87 | count = min_t(long, count, sizeof(event.lock_status)); | |
88 | ||
89 | if (copy_to_user(buf, &event, count)) | |
90 | return -EFAULT; | |
91 | ||
92 | return count; | |
93 | } | |
94 | ||
95 | static long | |
96 | hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, | |
594ddced TS |
97 | loff_t *offset) |
98 | { | |
99 | struct snd_efw *efw = hwdep->private_data; | |
100 | DEFINE_WAIT(wait); | |
594ddced TS |
101 | |
102 | spin_lock_irq(&efw->lock); | |
103 | ||
555e8a8f | 104 | while ((!efw->dev_lock_changed) && (efw->resp_queues == 0)) { |
594ddced TS |
105 | prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); |
106 | spin_unlock_irq(&efw->lock); | |
107 | schedule(); | |
108 | finish_wait(&efw->hwdep_wait, &wait); | |
109 | if (signal_pending(current)) | |
110 | return -ERESTARTSYS; | |
111 | spin_lock_irq(&efw->lock); | |
112 | } | |
113 | ||
555e8a8f TS |
114 | if (efw->dev_lock_changed) |
115 | count = hwdep_read_locked(efw, buf, count, offset); | |
116 | else if (efw->resp_queues > 0) | |
117 | count = hwdep_read_resp_buf(efw, buf, count, offset); | |
594ddced TS |
118 | |
119 | spin_unlock_irq(&efw->lock); | |
120 | ||
555e8a8f TS |
121 | return count; |
122 | } | |
594ddced | 123 | |
555e8a8f TS |
124 | static long |
125 | hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count, | |
126 | loff_t *offset) | |
127 | { | |
128 | struct snd_efw *efw = hwdep->private_data; | |
129 | u32 seqnum; | |
130 | u8 *buf; | |
131 | ||
132 | if (count < sizeof(struct snd_efw_transaction) || | |
133 | SND_EFW_RESPONSE_MAXIMUM_BYTES < count) | |
134 | return -EINVAL; | |
135 | ||
136 | buf = memdup_user(data, count); | |
137 | if (IS_ERR(buf)) | |
ba06b2cb | 138 | return PTR_ERR(buf); |
555e8a8f TS |
139 | |
140 | /* check seqnum is not for kernel-land */ | |
141 | seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum); | |
142 | if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) { | |
143 | count = -EINVAL; | |
144 | goto end; | |
145 | } | |
146 | ||
147 | if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0) | |
148 | count = -EIO; | |
149 | end: | |
150 | kfree(buf); | |
594ddced TS |
151 | return count; |
152 | } | |
153 | ||
154 | static unsigned int | |
155 | hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) | |
156 | { | |
157 | struct snd_efw *efw = hwdep->private_data; | |
158 | unsigned int events; | |
159 | ||
160 | poll_wait(file, &efw->hwdep_wait, wait); | |
161 | ||
162 | spin_lock_irq(&efw->lock); | |
555e8a8f | 163 | if (efw->dev_lock_changed || (efw->resp_queues > 0)) |
594ddced TS |
164 | events = POLLIN | POLLRDNORM; |
165 | else | |
166 | events = 0; | |
167 | spin_unlock_irq(&efw->lock); | |
168 | ||
555e8a8f | 169 | return events | POLLOUT; |
594ddced TS |
170 | } |
171 | ||
172 | static int | |
173 | hwdep_get_info(struct snd_efw *efw, void __user *arg) | |
174 | { | |
175 | struct fw_device *dev = fw_parent_device(efw->unit); | |
176 | struct snd_firewire_get_info info; | |
177 | ||
178 | memset(&info, 0, sizeof(info)); | |
179 | info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS; | |
180 | info.card = dev->card->index; | |
181 | *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); | |
182 | *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); | |
183 | strlcpy(info.device_name, dev_name(&dev->device), | |
184 | sizeof(info.device_name)); | |
185 | ||
186 | if (copy_to_user(arg, &info, sizeof(info))) | |
187 | return -EFAULT; | |
188 | ||
189 | return 0; | |
190 | } | |
191 | ||
192 | static int | |
193 | hwdep_lock(struct snd_efw *efw) | |
194 | { | |
195 | int err; | |
196 | ||
197 | spin_lock_irq(&efw->lock); | |
198 | ||
199 | if (efw->dev_lock_count == 0) { | |
200 | efw->dev_lock_count = -1; | |
201 | err = 0; | |
202 | } else { | |
203 | err = -EBUSY; | |
204 | } | |
205 | ||
206 | spin_unlock_irq(&efw->lock); | |
207 | ||
208 | return err; | |
209 | } | |
210 | ||
211 | static int | |
212 | hwdep_unlock(struct snd_efw *efw) | |
213 | { | |
214 | int err; | |
215 | ||
216 | spin_lock_irq(&efw->lock); | |
217 | ||
218 | if (efw->dev_lock_count == -1) { | |
219 | efw->dev_lock_count = 0; | |
220 | err = 0; | |
221 | } else { | |
222 | err = -EBADFD; | |
223 | } | |
224 | ||
225 | spin_unlock_irq(&efw->lock); | |
226 | ||
227 | return err; | |
228 | } | |
229 | ||
230 | static int | |
231 | hwdep_release(struct snd_hwdep *hwdep, struct file *file) | |
232 | { | |
233 | struct snd_efw *efw = hwdep->private_data; | |
234 | ||
235 | spin_lock_irq(&efw->lock); | |
236 | if (efw->dev_lock_count == -1) | |
237 | efw->dev_lock_count = 0; | |
238 | spin_unlock_irq(&efw->lock); | |
239 | ||
240 | return 0; | |
241 | } | |
242 | ||
243 | static int | |
244 | hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, | |
245 | unsigned int cmd, unsigned long arg) | |
246 | { | |
247 | struct snd_efw *efw = hwdep->private_data; | |
248 | ||
249 | switch (cmd) { | |
250 | case SNDRV_FIREWIRE_IOCTL_GET_INFO: | |
251 | return hwdep_get_info(efw, (void __user *)arg); | |
252 | case SNDRV_FIREWIRE_IOCTL_LOCK: | |
253 | return hwdep_lock(efw); | |
254 | case SNDRV_FIREWIRE_IOCTL_UNLOCK: | |
255 | return hwdep_unlock(efw); | |
256 | default: | |
257 | return -ENOIOCTLCMD; | |
258 | } | |
259 | } | |
260 | ||
261 | #ifdef CONFIG_COMPAT | |
262 | static int | |
263 | hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, | |
264 | unsigned int cmd, unsigned long arg) | |
265 | { | |
266 | return hwdep_ioctl(hwdep, file, cmd, | |
267 | (unsigned long)compat_ptr(arg)); | |
268 | } | |
269 | #else | |
270 | #define hwdep_compat_ioctl NULL | |
271 | #endif | |
272 | ||
273 | static const struct snd_hwdep_ops hwdep_ops = { | |
274 | .read = hwdep_read, | |
555e8a8f | 275 | .write = hwdep_write, |
594ddced TS |
276 | .release = hwdep_release, |
277 | .poll = hwdep_poll, | |
278 | .ioctl = hwdep_ioctl, | |
279 | .ioctl_compat = hwdep_compat_ioctl, | |
280 | }; | |
281 | ||
282 | int snd_efw_create_hwdep_device(struct snd_efw *efw) | |
283 | { | |
284 | struct snd_hwdep *hwdep; | |
285 | int err; | |
286 | ||
287 | err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep); | |
288 | if (err < 0) | |
289 | goto end; | |
290 | strcpy(hwdep->name, "Fireworks"); | |
291 | hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS; | |
292 | hwdep->ops = hwdep_ops; | |
293 | hwdep->private_data = efw; | |
294 | hwdep->exclusive = true; | |
295 | end: | |
296 | return err; | |
297 | } | |
298 |