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