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