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 134 poll_wait(file, &tscm->hwdep_wait, wait); 135 136 guard(spinlock_irq)(&tscm->lock); 137 if (tscm->dev_lock_changed || tscm->push_pos != tscm->pull_pos) 138 return EPOLLIN | EPOLLRDNORM; 139 else 140 return 0; 141 } 142 143 static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg) 144 { 145 struct fw_device *dev = fw_parent_device(tscm->unit); 146 struct snd_firewire_get_info info; 147 148 memset(&info, 0, sizeof(info)); 149 info.type = SNDRV_FIREWIRE_TYPE_TASCAM; 150 info.card = dev->card->index; 151 *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); 152 *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); 153 strscpy(info.device_name, dev_name(&dev->device), 154 sizeof(info.device_name)); 155 156 if (copy_to_user(arg, &info, sizeof(info))) 157 return -EFAULT; 158 159 return 0; 160 } 161 162 static int hwdep_lock(struct snd_tscm *tscm) 163 { 164 guard(spinlock_irq)(&tscm->lock); 165 166 if (tscm->dev_lock_count == 0) { 167 tscm->dev_lock_count = -1; 168 return 0; 169 } else { 170 return -EBUSY; 171 } 172 } 173 174 static int hwdep_unlock(struct snd_tscm *tscm) 175 { 176 guard(spinlock_irq)(&tscm->lock); 177 178 if (tscm->dev_lock_count == -1) { 179 tscm->dev_lock_count = 0; 180 return 0; 181 } else { 182 return -EBADFD; 183 } 184 } 185 186 static int tscm_hwdep_state(struct snd_tscm *tscm, void __user *arg) 187 { 188 if (copy_to_user(arg, tscm->state, sizeof(tscm->state))) 189 return -EFAULT; 190 191 return 0; 192 } 193 194 static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) 195 { 196 struct snd_tscm *tscm = hwdep->private_data; 197 198 guard(spinlock_irq)(&tscm->lock); 199 if (tscm->dev_lock_count == -1) 200 tscm->dev_lock_count = 0; 201 202 return 0; 203 } 204 205 static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, 206 unsigned int cmd, unsigned long arg) 207 { 208 struct snd_tscm *tscm = hwdep->private_data; 209 210 switch (cmd) { 211 case SNDRV_FIREWIRE_IOCTL_GET_INFO: 212 return hwdep_get_info(tscm, (void __user *)arg); 213 case SNDRV_FIREWIRE_IOCTL_LOCK: 214 return hwdep_lock(tscm); 215 case SNDRV_FIREWIRE_IOCTL_UNLOCK: 216 return hwdep_unlock(tscm); 217 case SNDRV_FIREWIRE_IOCTL_TASCAM_STATE: 218 return tscm_hwdep_state(tscm, (void __user *)arg); 219 default: 220 return -ENOIOCTLCMD; 221 } 222 } 223 224 #ifdef CONFIG_COMPAT 225 static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, 226 unsigned int cmd, unsigned long arg) 227 { 228 return hwdep_ioctl(hwdep, file, cmd, 229 (unsigned long)compat_ptr(arg)); 230 } 231 #else 232 #define hwdep_compat_ioctl NULL 233 #endif 234 235 int snd_tscm_create_hwdep_device(struct snd_tscm *tscm) 236 { 237 static const struct snd_hwdep_ops ops = { 238 .read = hwdep_read, 239 .release = hwdep_release, 240 .poll = hwdep_poll, 241 .ioctl = hwdep_ioctl, 242 .ioctl_compat = hwdep_compat_ioctl, 243 }; 244 struct snd_hwdep *hwdep; 245 int err; 246 247 err = snd_hwdep_new(tscm->card, "Tascam", 0, &hwdep); 248 if (err < 0) 249 return err; 250 251 strscpy(hwdep->name, "Tascam"); 252 hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM; 253 hwdep->ops = ops; 254 hwdep->private_data = tscm; 255 hwdep->exclusive = true; 256 257 tscm->hwdep = hwdep; 258 259 return err; 260 } 261