.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * tascam-hwdep.c - a part of driver for TASCAM FireWire series |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright (c) 2015 Takashi Sakamoto |
---|
5 | | - * |
---|
6 | | - * Licensed under the terms of the GNU General Public License, version 2. |
---|
7 | 6 | */ |
---|
8 | 7 | |
---|
9 | 8 | /* |
---|
.. | .. |
---|
16 | 15 | |
---|
17 | 16 | #include "tascam.h" |
---|
18 | 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 | + |
---|
19 | 98 | static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, |
---|
20 | 99 | loff_t *offset) |
---|
21 | 100 | { |
---|
22 | 101 | struct snd_tscm *tscm = hwdep->private_data; |
---|
23 | 102 | DEFINE_WAIT(wait); |
---|
24 | | - union snd_firewire_event event = { |
---|
25 | | - .lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS, |
---|
26 | | - }; |
---|
27 | 103 | |
---|
28 | 104 | spin_lock_irq(&tscm->lock); |
---|
29 | 105 | |
---|
30 | | - while (!tscm->dev_lock_changed) { |
---|
| 106 | + while (!tscm->dev_lock_changed && tscm->push_pos == tscm->pull_pos) { |
---|
31 | 107 | prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE); |
---|
32 | 108 | spin_unlock_irq(&tscm->lock); |
---|
33 | 109 | schedule(); |
---|
.. | .. |
---|
37 | 113 | spin_lock_irq(&tscm->lock); |
---|
38 | 114 | } |
---|
39 | 115 | |
---|
40 | | - event.lock_status.status = (tscm->dev_lock_count > 0); |
---|
41 | | - tscm->dev_lock_changed = false; |
---|
42 | | - |
---|
43 | | - spin_unlock_irq(&tscm->lock); |
---|
44 | | - |
---|
45 | | - count = min_t(long, count, sizeof(event.lock_status)); |
---|
46 | | - |
---|
47 | | - if (copy_to_user(buf, &event, count)) |
---|
48 | | - return -EFAULT; |
---|
| 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 | + } |
---|
49 | 125 | |
---|
50 | 126 | return count; |
---|
51 | 127 | } |
---|
.. | .. |
---|
59 | 135 | poll_wait(file, &tscm->hwdep_wait, wait); |
---|
60 | 136 | |
---|
61 | 137 | spin_lock_irq(&tscm->lock); |
---|
62 | | - if (tscm->dev_lock_changed) |
---|
| 138 | + if (tscm->dev_lock_changed || tscm->push_pos != tscm->pull_pos) |
---|
63 | 139 | events = EPOLLIN | EPOLLRDNORM; |
---|
64 | 140 | else |
---|
65 | 141 | events = 0; |
---|
.. | .. |
---|
123 | 199 | return err; |
---|
124 | 200 | } |
---|
125 | 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 | + |
---|
126 | 210 | static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) |
---|
127 | 211 | { |
---|
128 | 212 | struct snd_tscm *tscm = hwdep->private_data; |
---|
.. | .. |
---|
147 | 231 | return hwdep_lock(tscm); |
---|
148 | 232 | case SNDRV_FIREWIRE_IOCTL_UNLOCK: |
---|
149 | 233 | return hwdep_unlock(tscm); |
---|
| 234 | + case SNDRV_FIREWIRE_IOCTL_TASCAM_STATE: |
---|
| 235 | + return tscm_hwdep_state(tscm, (void __user *)arg); |
---|
150 | 236 | default: |
---|
151 | 237 | return -ENOIOCTLCMD; |
---|
152 | 238 | } |
---|
.. | .. |
---|
185 | 271 | hwdep->private_data = tscm; |
---|
186 | 272 | hwdep->exclusive = true; |
---|
187 | 273 | |
---|
| 274 | + tscm->hwdep = hwdep; |
---|
| 275 | + |
---|
188 | 276 | return err; |
---|
189 | 277 | } |
---|