Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/firewire/fireworks/fireworks_hwdep.c
29266 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
scoped_guard(spinlock_irq, &efw->lock) {
107
event.lock_status.status = (efw->dev_lock_count > 0);
108
efw->dev_lock_changed = false;
109
}
110
111
count = min_t(long, count, sizeof(event.lock_status));
112
113
if (copy_to_user(buf, &event, count))
114
return -EFAULT;
115
116
return count;
117
}
118
119
static long
120
hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
121
loff_t *offset)
122
{
123
struct snd_efw *efw = hwdep->private_data;
124
DEFINE_WAIT(wait);
125
bool dev_lock_changed;
126
bool queued;
127
128
spin_lock_irq(&efw->lock);
129
130
dev_lock_changed = efw->dev_lock_changed;
131
queued = efw->push_ptr != efw->pull_ptr;
132
133
while (!dev_lock_changed && !queued) {
134
prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
135
spin_unlock_irq(&efw->lock);
136
schedule();
137
finish_wait(&efw->hwdep_wait, &wait);
138
if (signal_pending(current))
139
return -ERESTARTSYS;
140
spin_lock_irq(&efw->lock);
141
dev_lock_changed = efw->dev_lock_changed;
142
queued = efw->push_ptr != efw->pull_ptr;
143
}
144
145
spin_unlock_irq(&efw->lock);
146
147
if (dev_lock_changed)
148
count = hwdep_read_locked(efw, buf, count, offset);
149
else if (queued)
150
count = hwdep_read_resp_buf(efw, buf, count, offset);
151
152
return count;
153
}
154
155
static long
156
hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
157
loff_t *offset)
158
{
159
struct snd_efw *efw = hwdep->private_data;
160
u32 seqnum;
161
u8 *buf;
162
163
if (count < sizeof(struct snd_efw_transaction) ||
164
SND_EFW_RESPONSE_MAXIMUM_BYTES < count)
165
return -EINVAL;
166
167
buf = memdup_user(data, count);
168
if (IS_ERR(buf))
169
return PTR_ERR(buf);
170
171
/* check seqnum is not for kernel-land */
172
seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum);
173
if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) {
174
count = -EINVAL;
175
goto end;
176
}
177
178
if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0)
179
count = -EIO;
180
end:
181
kfree(buf);
182
return count;
183
}
184
185
static __poll_t
186
hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
187
{
188
struct snd_efw *efw = hwdep->private_data;
189
__poll_t events;
190
191
poll_wait(file, &efw->hwdep_wait, wait);
192
193
guard(spinlock_irq)(&efw->lock);
194
if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr)
195
events = EPOLLIN | EPOLLRDNORM;
196
else
197
events = 0;
198
return events | EPOLLOUT;
199
}
200
201
static int
202
hwdep_get_info(struct snd_efw *efw, void __user *arg)
203
{
204
struct fw_device *dev = fw_parent_device(efw->unit);
205
struct snd_firewire_get_info info;
206
207
memset(&info, 0, sizeof(info));
208
info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS;
209
info.card = dev->card->index;
210
*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
211
*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
212
strscpy(info.device_name, dev_name(&dev->device),
213
sizeof(info.device_name));
214
215
if (copy_to_user(arg, &info, sizeof(info)))
216
return -EFAULT;
217
218
return 0;
219
}
220
221
static int
222
hwdep_lock(struct snd_efw *efw)
223
{
224
guard(spinlock_irq)(&efw->lock);
225
226
if (efw->dev_lock_count == 0) {
227
efw->dev_lock_count = -1;
228
return 0;
229
} else {
230
return -EBUSY;
231
}
232
}
233
234
static int
235
hwdep_unlock(struct snd_efw *efw)
236
{
237
guard(spinlock_irq)(&efw->lock);
238
239
if (efw->dev_lock_count == -1) {
240
efw->dev_lock_count = 0;
241
return 0;
242
} else {
243
return -EBADFD;
244
}
245
}
246
247
static int
248
hwdep_release(struct snd_hwdep *hwdep, struct file *file)
249
{
250
struct snd_efw *efw = hwdep->private_data;
251
252
guard(spinlock_irq)(&efw->lock);
253
if (efw->dev_lock_count == -1)
254
efw->dev_lock_count = 0;
255
256
return 0;
257
}
258
259
static int
260
hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
261
unsigned int cmd, unsigned long arg)
262
{
263
struct snd_efw *efw = hwdep->private_data;
264
265
switch (cmd) {
266
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
267
return hwdep_get_info(efw, (void __user *)arg);
268
case SNDRV_FIREWIRE_IOCTL_LOCK:
269
return hwdep_lock(efw);
270
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
271
return hwdep_unlock(efw);
272
default:
273
return -ENOIOCTLCMD;
274
}
275
}
276
277
#ifdef CONFIG_COMPAT
278
static int
279
hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
280
unsigned int cmd, unsigned long arg)
281
{
282
return hwdep_ioctl(hwdep, file, cmd,
283
(unsigned long)compat_ptr(arg));
284
}
285
#else
286
#define hwdep_compat_ioctl NULL
287
#endif
288
289
int snd_efw_create_hwdep_device(struct snd_efw *efw)
290
{
291
static const struct snd_hwdep_ops ops = {
292
.read = hwdep_read,
293
.write = hwdep_write,
294
.release = hwdep_release,
295
.poll = hwdep_poll,
296
.ioctl = hwdep_ioctl,
297
.ioctl_compat = hwdep_compat_ioctl,
298
};
299
struct snd_hwdep *hwdep;
300
int err;
301
302
err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);
303
if (err < 0)
304
goto end;
305
strscpy(hwdep->name, "Fireworks");
306
hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;
307
hwdep->ops = ops;
308
hwdep->private_data = efw;
309
hwdep->exclusive = true;
310
end:
311
return err;
312
}
313
314
315