Linux-libre 5.3.12-gnu
[librecmc/linux-libre.git] / sound / firewire / tascam / tascam-hwdep.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * tascam-hwdep.c - a part of driver for TASCAM FireWire series
4  *
5  * Copyright (c) 2015 Takashi Sakamoto
6  */
7
8 /*
9  * This codes give three functionality.
10  *
11  * 1.get firewire node information
12  * 2.get notification about starting/stopping stream
13  * 3.lock/unlock stream
14  */
15
16 #include "tascam.h"
17
18 static long tscm_hwdep_read_locked(struct snd_tscm *tscm, char __user *buf,
19                                    long count, loff_t *offset)
20 {
21         struct snd_firewire_event_lock_status event = {
22                 .type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,
23         };
24
25         event.status = (tscm->dev_lock_count > 0);
26         tscm->dev_lock_changed = false;
27         count = min_t(long, count, sizeof(event));
28
29         spin_unlock_irq(&tscm->lock);
30
31         if (copy_to_user(buf, &event, count))
32                 return -EFAULT;
33
34         return count;
35 }
36
37 static long tscm_hwdep_read_queue(struct snd_tscm *tscm, char __user *buf,
38                                   long remained, loff_t *offset)
39 {
40         char __user *pos = buf;
41         unsigned int type = SNDRV_FIREWIRE_EVENT_TASCAM_CONTROL;
42         struct snd_firewire_tascam_change *entries = tscm->queue;
43         long count;
44
45         // At least, one control event can be copied.
46         if (remained < sizeof(type) + sizeof(*entries)) {
47                 spin_unlock_irq(&tscm->lock);
48                 return -EINVAL;
49         }
50
51         // Copy the type field later.
52         count = sizeof(type);
53         remained -= sizeof(type);
54         pos += sizeof(type);
55
56         while (true) {
57                 unsigned int head_pos;
58                 unsigned int tail_pos;
59                 unsigned int length;
60
61                 if (tscm->pull_pos == tscm->push_pos)
62                         break;
63                 else if (tscm->pull_pos < tscm->push_pos)
64                         tail_pos = tscm->push_pos;
65                 else
66                         tail_pos = SND_TSCM_QUEUE_COUNT;
67                 head_pos = tscm->pull_pos;
68
69                 length = (tail_pos - head_pos) * sizeof(*entries);
70                 if (remained < length)
71                         length = rounddown(remained, sizeof(*entries));
72                 if (length == 0)
73                         break;
74
75                 spin_unlock_irq(&tscm->lock);
76                 if (copy_to_user(pos, &entries[head_pos], length))
77                         return -EFAULT;
78
79                 spin_lock_irq(&tscm->lock);
80
81                 tscm->pull_pos = tail_pos % SND_TSCM_QUEUE_COUNT;
82
83                 count += length;
84                 remained -= length;
85                 pos += length;
86         }
87
88         spin_unlock_irq(&tscm->lock);
89
90         if (copy_to_user(buf, &type, sizeof(type)))
91                 return -EFAULT;
92
93         return count;
94 }
95
96 static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
97                        loff_t *offset)
98 {
99         struct snd_tscm *tscm = hwdep->private_data;
100         DEFINE_WAIT(wait);
101
102         spin_lock_irq(&tscm->lock);
103
104         while (!tscm->dev_lock_changed && tscm->push_pos == tscm->pull_pos) {
105                 prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
106                 spin_unlock_irq(&tscm->lock);
107                 schedule();
108                 finish_wait(&tscm->hwdep_wait, &wait);
109                 if (signal_pending(current))
110                         return -ERESTARTSYS;
111                 spin_lock_irq(&tscm->lock);
112         }
113
114         // NOTE: The acquired lock should be released in callee side.
115         if (tscm->dev_lock_changed) {
116                 count = tscm_hwdep_read_locked(tscm, buf, count, offset);
117         } else if (tscm->push_pos != tscm->pull_pos) {
118                 count = tscm_hwdep_read_queue(tscm, buf, count, offset);
119         } else {
120                 spin_unlock_irq(&tscm->lock);
121                 count = 0;
122         }
123
124         return count;
125 }
126
127 static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
128                                poll_table *wait)
129 {
130         struct snd_tscm *tscm = hwdep->private_data;
131         __poll_t events;
132
133         poll_wait(file, &tscm->hwdep_wait, wait);
134
135         spin_lock_irq(&tscm->lock);
136         if (tscm->dev_lock_changed || tscm->push_pos != tscm->pull_pos)
137                 events = EPOLLIN | EPOLLRDNORM;
138         else
139                 events = 0;
140         spin_unlock_irq(&tscm->lock);
141
142         return events;
143 }
144
145 static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg)
146 {
147         struct fw_device *dev = fw_parent_device(tscm->unit);
148         struct snd_firewire_get_info info;
149
150         memset(&info, 0, sizeof(info));
151         info.type = SNDRV_FIREWIRE_TYPE_TASCAM;
152         info.card = dev->card->index;
153         *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
154         *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
155         strlcpy(info.device_name, dev_name(&dev->device),
156                 sizeof(info.device_name));
157
158         if (copy_to_user(arg, &info, sizeof(info)))
159                 return -EFAULT;
160
161         return 0;
162 }
163
164 static int hwdep_lock(struct snd_tscm *tscm)
165 {
166         int err;
167
168         spin_lock_irq(&tscm->lock);
169
170         if (tscm->dev_lock_count == 0) {
171                 tscm->dev_lock_count = -1;
172                 err = 0;
173         } else {
174                 err = -EBUSY;
175         }
176
177         spin_unlock_irq(&tscm->lock);
178
179         return err;
180 }
181
182 static int hwdep_unlock(struct snd_tscm *tscm)
183 {
184         int err;
185
186         spin_lock_irq(&tscm->lock);
187
188         if (tscm->dev_lock_count == -1) {
189                 tscm->dev_lock_count = 0;
190                 err = 0;
191         } else {
192                 err = -EBADFD;
193         }
194
195         spin_unlock_irq(&tscm->lock);
196
197         return err;
198 }
199
200 static int tscm_hwdep_state(struct snd_tscm *tscm, void __user *arg)
201 {
202         if (copy_to_user(arg, tscm->state, sizeof(tscm->state)))
203                 return -EFAULT;
204
205         return 0;
206 }
207
208 static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
209 {
210         struct snd_tscm *tscm = hwdep->private_data;
211
212         spin_lock_irq(&tscm->lock);
213         if (tscm->dev_lock_count == -1)
214                 tscm->dev_lock_count = 0;
215         spin_unlock_irq(&tscm->lock);
216
217         return 0;
218 }
219
220 static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
221             unsigned int cmd, unsigned long arg)
222 {
223         struct snd_tscm *tscm = hwdep->private_data;
224
225         switch (cmd) {
226         case SNDRV_FIREWIRE_IOCTL_GET_INFO:
227                 return hwdep_get_info(tscm, (void __user *)arg);
228         case SNDRV_FIREWIRE_IOCTL_LOCK:
229                 return hwdep_lock(tscm);
230         case SNDRV_FIREWIRE_IOCTL_UNLOCK:
231                 return hwdep_unlock(tscm);
232         case SNDRV_FIREWIRE_IOCTL_TASCAM_STATE:
233                 return tscm_hwdep_state(tscm, (void __user *)arg);
234         default:
235                 return -ENOIOCTLCMD;
236         }
237 }
238
239 #ifdef CONFIG_COMPAT
240 static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
241                               unsigned int cmd, unsigned long arg)
242 {
243         return hwdep_ioctl(hwdep, file, cmd,
244                            (unsigned long)compat_ptr(arg));
245 }
246 #else
247 #define hwdep_compat_ioctl NULL
248 #endif
249
250 int snd_tscm_create_hwdep_device(struct snd_tscm *tscm)
251 {
252         static const struct snd_hwdep_ops ops = {
253                 .read           = hwdep_read,
254                 .release        = hwdep_release,
255                 .poll           = hwdep_poll,
256                 .ioctl          = hwdep_ioctl,
257                 .ioctl_compat   = hwdep_compat_ioctl,
258         };
259         struct snd_hwdep *hwdep;
260         int err;
261
262         err = snd_hwdep_new(tscm->card, "Tascam", 0, &hwdep);
263         if (err < 0)
264                 return err;
265
266         strcpy(hwdep->name, "Tascam");
267         hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM;
268         hwdep->ops = ops;
269         hwdep->private_data = tscm;
270         hwdep->exclusive = true;
271
272         tscm->hwdep = hwdep;
273
274         return err;
275 }