| .. | .. |
|---|
| 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 | } |
|---|