Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/firewire/fireworks/fireworks_hwdep.c
26424 views
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(buf, &type, 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(&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(&efw->lock);
66
67
if (copy_to_user(buf, pull_ptr, till_end))
68
return -EFAULT;
69
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;
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(&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(&efw->lock);
107
108
event.lock_status.status = (efw->dev_lock_count > 0);
109
efw->dev_lock_changed = false;
110
111
spin_unlock_irq(&efw->lock);
112
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,
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(&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(&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);
143
dev_lock_changed = efw->dev_lock_changed;
144
queued = efw->push_ptr != efw->pull_ptr;
145
}
146
147
spin_unlock_irq(&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, 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(buf))
171
return PTR_ERR(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(efw->unit, buf, count) < 0)
181
count = -EIO;
182
end:
183
kfree(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(file, &efw->hwdep_wait, wait);
194
195
spin_lock_irq(&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(&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(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
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(efw->card, "Fireworks", 0, &hwdep);
320
if (err < 0)
321
goto end;
322
strscpy(hwdep->name, "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
332