1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * fireworks_hwdep.c - a part of driver for Fireworks based devices |
4 | * |
5 | * Copyright (c) 2013-2014 Takashi Sakamoto |
6 | */ |
7 | |
8 | /* |
9 | * This codes have five functionalities. |
10 | * |
11 | * 1.get information about firewire node |
12 | * 2.get notification about starting/stopping stream |
13 | * 3.lock/unlock streaming |
14 | * 4.transmit command of EFW transaction |
15 | * 5.receive response of EFW transaction |
16 | * |
17 | */ |
18 | |
19 | #include "fireworks.h" |
20 | |
21 | static long |
22 | hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained, |
23 | loff_t *offset) |
24 | { |
25 | unsigned int length, till_end, type; |
26 | struct snd_efw_transaction *t; |
27 | u8 *pull_ptr; |
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(to: buf, from: &type, n: sizeof(type))) |
36 | return -EFAULT; |
37 | count += sizeof(type); |
38 | remained -= sizeof(type); |
39 | buf += sizeof(type); |
40 | |
41 | /* write into buffer as many responses as possible */ |
42 | spin_lock_irq(lock: &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); |
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 - |
62 | (unsigned int)(pull_ptr - efw->resp_buf); |
63 | till_end = min_t(unsigned int, length, till_end); |
64 | |
65 | spin_unlock_irq(lock: &efw->lock); |
66 | |
67 | if (copy_to_user(to: buf, from: pull_ptr, n: till_end)) |
68 | return -EFAULT; |
69 | |
70 | spin_lock_irq(lock: &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; |
75 | |
76 | length -= till_end; |
77 | buf += till_end; |
78 | count += till_end; |
79 | remained -= till_end; |
80 | } |
81 | } |
82 | |
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(lock: &efw->lock); |
94 | |
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 | { |
102 | union snd_firewire_event event = { |
103 | .lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS, |
104 | }; |
105 | |
106 | spin_lock_irq(lock: &efw->lock); |
107 | |
108 | event.lock_status.status = (efw->dev_lock_count > 0); |
109 | efw->dev_lock_changed = false; |
110 | |
111 | spin_unlock_irq(lock: &efw->lock); |
112 | |
113 | count = min_t(long, count, sizeof(event.lock_status)); |
114 | |
115 | if (copy_to_user(to: buf, from: &event, n: 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, |
123 | loff_t *offset) |
124 | { |
125 | struct snd_efw *efw = hwdep->private_data; |
126 | DEFINE_WAIT(wait); |
127 | bool dev_lock_changed; |
128 | bool queued; |
129 | |
130 | spin_lock_irq(lock: &efw->lock); |
131 | |
132 | dev_lock_changed = efw->dev_lock_changed; |
133 | queued = efw->push_ptr != efw->pull_ptr; |
134 | |
135 | while (!dev_lock_changed && !queued) { |
136 | prepare_to_wait(wq_head: &efw->hwdep_wait, wq_entry: &wait, TASK_INTERRUPTIBLE); |
137 | spin_unlock_irq(lock: &efw->lock); |
138 | schedule(); |
139 | finish_wait(wq_head: &efw->hwdep_wait, wq_entry: &wait); |
140 | if (signal_pending(current)) |
141 | return -ERESTARTSYS; |
142 | spin_lock_irq(lock: &efw->lock); |
143 | dev_lock_changed = efw->dev_lock_changed; |
144 | queued = efw->push_ptr != efw->pull_ptr; |
145 | } |
146 | |
147 | spin_unlock_irq(lock: &efw->lock); |
148 | |
149 | if (dev_lock_changed) |
150 | count = hwdep_read_locked(efw, buf, count, offset); |
151 | else if (queued) |
152 | count = hwdep_read_resp_buf(efw, buf, remained: count, offset); |
153 | |
154 | return count; |
155 | } |
156 | |
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(ptr: buf)) |
171 | return PTR_ERR(ptr: buf); |
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(unit: efw->unit, cmd: buf, size: count) < 0) |
181 | count = -EIO; |
182 | end: |
183 | kfree(objp: buf); |
184 | return count; |
185 | } |
186 | |
187 | static __poll_t |
188 | hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) |
189 | { |
190 | struct snd_efw *efw = hwdep->private_data; |
191 | __poll_t events; |
192 | |
193 | poll_wait(filp: file, wait_address: &efw->hwdep_wait, p: wait); |
194 | |
195 | spin_lock_irq(lock: &efw->lock); |
196 | if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr) |
197 | events = EPOLLIN | EPOLLRDNORM; |
198 | else |
199 | events = 0; |
200 | spin_unlock_irq(lock: &efw->lock); |
201 | |
202 | return events | EPOLLOUT; |
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 | strscpy(info.device_name, dev_name(&dev->device), |
217 | sizeof(info.device_name)); |
218 | |
219 | if (copy_to_user(to: arg, from: &info, n: 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(lock: &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(lock: &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(lock: &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(lock: &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(lock: &efw->lock); |
269 | if (efw->dev_lock_count == -1) |
270 | efw->dev_lock_count = 0; |
271 | spin_unlock_irq(lock: &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, arg: (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 | arg: (unsigned long)compat_ptr(uptr: arg)); |
301 | } |
302 | #else |
303 | #define hwdep_compat_ioctl NULL |
304 | #endif |
305 | |
306 | int snd_efw_create_hwdep_device(struct snd_efw *efw) |
307 | { |
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 | }; |
316 | struct snd_hwdep *hwdep; |
317 | int err; |
318 | |
319 | err = snd_hwdep_new(card: efw->card, id: "Fireworks" , device: 0, rhwdep: &hwdep); |
320 | if (err < 0) |
321 | goto end; |
322 | strcpy(p: hwdep->name, q: "Fireworks" ); |
323 | hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS; |
324 | hwdep->ops = ops; |
325 | hwdep->private_data = efw; |
326 | hwdep->exclusive = true; |
327 | end: |
328 | return err; |
329 | } |
330 | |
331 | |