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