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