tascam-hwdep.c (6214B)
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 18static 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 38static 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 98static 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 129static __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 147static 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 strscpy(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 166static 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 184static 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 202static 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 210static 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 222static 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 242static 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 252int 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}