162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * tascam-hwdep.c - a part of driver for TASCAM FireWire series 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2015 Takashi Sakamoto 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci/* 962306a36Sopenharmony_ci * This codes give three functionality. 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * 1.get firewire node information 1262306a36Sopenharmony_ci * 2.get notification about starting/stopping stream 1362306a36Sopenharmony_ci * 3.lock/unlock stream 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include "tascam.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistatic long tscm_hwdep_read_locked(struct snd_tscm *tscm, char __user *buf, 1962306a36Sopenharmony_ci long count, loff_t *offset) 2062306a36Sopenharmony_ci __releases(&tscm->lock) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci struct snd_firewire_event_lock_status event = { 2362306a36Sopenharmony_ci .type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS, 2462306a36Sopenharmony_ci }; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci event.status = (tscm->dev_lock_count > 0); 2762306a36Sopenharmony_ci tscm->dev_lock_changed = false; 2862306a36Sopenharmony_ci count = min_t(long, count, sizeof(event)); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci spin_unlock_irq(&tscm->lock); 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci if (copy_to_user(buf, &event, count)) 3362306a36Sopenharmony_ci return -EFAULT; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci return count; 3662306a36Sopenharmony_ci} 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic long tscm_hwdep_read_queue(struct snd_tscm *tscm, char __user *buf, 3962306a36Sopenharmony_ci long remained, loff_t *offset) 4062306a36Sopenharmony_ci __releases(&tscm->lock) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci char __user *pos = buf; 4362306a36Sopenharmony_ci unsigned int type = SNDRV_FIREWIRE_EVENT_TASCAM_CONTROL; 4462306a36Sopenharmony_ci struct snd_firewire_tascam_change *entries = tscm->queue; 4562306a36Sopenharmony_ci long count; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci // At least, one control event can be copied. 4862306a36Sopenharmony_ci if (remained < sizeof(type) + sizeof(*entries)) { 4962306a36Sopenharmony_ci spin_unlock_irq(&tscm->lock); 5062306a36Sopenharmony_ci return -EINVAL; 5162306a36Sopenharmony_ci } 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci // Copy the type field later. 5462306a36Sopenharmony_ci count = sizeof(type); 5562306a36Sopenharmony_ci remained -= sizeof(type); 5662306a36Sopenharmony_ci pos += sizeof(type); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci while (true) { 5962306a36Sopenharmony_ci unsigned int head_pos; 6062306a36Sopenharmony_ci unsigned int tail_pos; 6162306a36Sopenharmony_ci unsigned int length; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (tscm->pull_pos == tscm->push_pos) 6462306a36Sopenharmony_ci break; 6562306a36Sopenharmony_ci else if (tscm->pull_pos < tscm->push_pos) 6662306a36Sopenharmony_ci tail_pos = tscm->push_pos; 6762306a36Sopenharmony_ci else 6862306a36Sopenharmony_ci tail_pos = SND_TSCM_QUEUE_COUNT; 6962306a36Sopenharmony_ci head_pos = tscm->pull_pos; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci length = (tail_pos - head_pos) * sizeof(*entries); 7262306a36Sopenharmony_ci if (remained < length) 7362306a36Sopenharmony_ci length = rounddown(remained, sizeof(*entries)); 7462306a36Sopenharmony_ci if (length == 0) 7562306a36Sopenharmony_ci break; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci spin_unlock_irq(&tscm->lock); 7862306a36Sopenharmony_ci if (copy_to_user(pos, &entries[head_pos], length)) 7962306a36Sopenharmony_ci return -EFAULT; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci spin_lock_irq(&tscm->lock); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci tscm->pull_pos = tail_pos % SND_TSCM_QUEUE_COUNT; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci count += length; 8662306a36Sopenharmony_ci remained -= length; 8762306a36Sopenharmony_ci pos += length; 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci spin_unlock_irq(&tscm->lock); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci if (copy_to_user(buf, &type, sizeof(type))) 9362306a36Sopenharmony_ci return -EFAULT; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci return count; 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, 9962306a36Sopenharmony_ci loff_t *offset) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci struct snd_tscm *tscm = hwdep->private_data; 10262306a36Sopenharmony_ci DEFINE_WAIT(wait); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci spin_lock_irq(&tscm->lock); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci while (!tscm->dev_lock_changed && tscm->push_pos == tscm->pull_pos) { 10762306a36Sopenharmony_ci prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE); 10862306a36Sopenharmony_ci spin_unlock_irq(&tscm->lock); 10962306a36Sopenharmony_ci schedule(); 11062306a36Sopenharmony_ci finish_wait(&tscm->hwdep_wait, &wait); 11162306a36Sopenharmony_ci if (signal_pending(current)) 11262306a36Sopenharmony_ci return -ERESTARTSYS; 11362306a36Sopenharmony_ci spin_lock_irq(&tscm->lock); 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci // NOTE: The acquired lock should be released in callee side. 11762306a36Sopenharmony_ci if (tscm->dev_lock_changed) { 11862306a36Sopenharmony_ci count = tscm_hwdep_read_locked(tscm, buf, count, offset); 11962306a36Sopenharmony_ci } else if (tscm->push_pos != tscm->pull_pos) { 12062306a36Sopenharmony_ci count = tscm_hwdep_read_queue(tscm, buf, count, offset); 12162306a36Sopenharmony_ci } else { 12262306a36Sopenharmony_ci spin_unlock_irq(&tscm->lock); 12362306a36Sopenharmony_ci count = 0; 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci return count; 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, 13062306a36Sopenharmony_ci poll_table *wait) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci struct snd_tscm *tscm = hwdep->private_data; 13362306a36Sopenharmony_ci __poll_t events; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci poll_wait(file, &tscm->hwdep_wait, wait); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci spin_lock_irq(&tscm->lock); 13862306a36Sopenharmony_ci if (tscm->dev_lock_changed || tscm->push_pos != tscm->pull_pos) 13962306a36Sopenharmony_ci events = EPOLLIN | EPOLLRDNORM; 14062306a36Sopenharmony_ci else 14162306a36Sopenharmony_ci events = 0; 14262306a36Sopenharmony_ci spin_unlock_irq(&tscm->lock); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci return events; 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic int hwdep_get_info(struct snd_tscm *tscm, void __user *arg) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci struct fw_device *dev = fw_parent_device(tscm->unit); 15062306a36Sopenharmony_ci struct snd_firewire_get_info info; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci memset(&info, 0, sizeof(info)); 15362306a36Sopenharmony_ci info.type = SNDRV_FIREWIRE_TYPE_TASCAM; 15462306a36Sopenharmony_ci info.card = dev->card->index; 15562306a36Sopenharmony_ci *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); 15662306a36Sopenharmony_ci *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); 15762306a36Sopenharmony_ci strscpy(info.device_name, dev_name(&dev->device), 15862306a36Sopenharmony_ci sizeof(info.device_name)); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci if (copy_to_user(arg, &info, sizeof(info))) 16162306a36Sopenharmony_ci return -EFAULT; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci return 0; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic int hwdep_lock(struct snd_tscm *tscm) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci int err; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci spin_lock_irq(&tscm->lock); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci if (tscm->dev_lock_count == 0) { 17362306a36Sopenharmony_ci tscm->dev_lock_count = -1; 17462306a36Sopenharmony_ci err = 0; 17562306a36Sopenharmony_ci } else { 17662306a36Sopenharmony_ci err = -EBUSY; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci spin_unlock_irq(&tscm->lock); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci return err; 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic int hwdep_unlock(struct snd_tscm *tscm) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci int err; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci spin_lock_irq(&tscm->lock); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if (tscm->dev_lock_count == -1) { 19162306a36Sopenharmony_ci tscm->dev_lock_count = 0; 19262306a36Sopenharmony_ci err = 0; 19362306a36Sopenharmony_ci } else { 19462306a36Sopenharmony_ci err = -EBADFD; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci spin_unlock_irq(&tscm->lock); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci return err; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic int tscm_hwdep_state(struct snd_tscm *tscm, void __user *arg) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci if (copy_to_user(arg, tscm->state, sizeof(tscm->state))) 20562306a36Sopenharmony_ci return -EFAULT; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci return 0; 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_cistatic int hwdep_release(struct snd_hwdep *hwdep, struct file *file) 21162306a36Sopenharmony_ci{ 21262306a36Sopenharmony_ci struct snd_tscm *tscm = hwdep->private_data; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci spin_lock_irq(&tscm->lock); 21562306a36Sopenharmony_ci if (tscm->dev_lock_count == -1) 21662306a36Sopenharmony_ci tscm->dev_lock_count = 0; 21762306a36Sopenharmony_ci spin_unlock_irq(&tscm->lock); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci return 0; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, 22362306a36Sopenharmony_ci unsigned int cmd, unsigned long arg) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci struct snd_tscm *tscm = hwdep->private_data; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci switch (cmd) { 22862306a36Sopenharmony_ci case SNDRV_FIREWIRE_IOCTL_GET_INFO: 22962306a36Sopenharmony_ci return hwdep_get_info(tscm, (void __user *)arg); 23062306a36Sopenharmony_ci case SNDRV_FIREWIRE_IOCTL_LOCK: 23162306a36Sopenharmony_ci return hwdep_lock(tscm); 23262306a36Sopenharmony_ci case SNDRV_FIREWIRE_IOCTL_UNLOCK: 23362306a36Sopenharmony_ci return hwdep_unlock(tscm); 23462306a36Sopenharmony_ci case SNDRV_FIREWIRE_IOCTL_TASCAM_STATE: 23562306a36Sopenharmony_ci return tscm_hwdep_state(tscm, (void __user *)arg); 23662306a36Sopenharmony_ci default: 23762306a36Sopenharmony_ci return -ENOIOCTLCMD; 23862306a36Sopenharmony_ci } 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci#ifdef CONFIG_COMPAT 24262306a36Sopenharmony_cistatic int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, 24362306a36Sopenharmony_ci unsigned int cmd, unsigned long arg) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci return hwdep_ioctl(hwdep, file, cmd, 24662306a36Sopenharmony_ci (unsigned long)compat_ptr(arg)); 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci#else 24962306a36Sopenharmony_ci#define hwdep_compat_ioctl NULL 25062306a36Sopenharmony_ci#endif 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ciint snd_tscm_create_hwdep_device(struct snd_tscm *tscm) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci static const struct snd_hwdep_ops ops = { 25562306a36Sopenharmony_ci .read = hwdep_read, 25662306a36Sopenharmony_ci .release = hwdep_release, 25762306a36Sopenharmony_ci .poll = hwdep_poll, 25862306a36Sopenharmony_ci .ioctl = hwdep_ioctl, 25962306a36Sopenharmony_ci .ioctl_compat = hwdep_compat_ioctl, 26062306a36Sopenharmony_ci }; 26162306a36Sopenharmony_ci struct snd_hwdep *hwdep; 26262306a36Sopenharmony_ci int err; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci err = snd_hwdep_new(tscm->card, "Tascam", 0, &hwdep); 26562306a36Sopenharmony_ci if (err < 0) 26662306a36Sopenharmony_ci return err; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci strcpy(hwdep->name, "Tascam"); 26962306a36Sopenharmony_ci hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM; 27062306a36Sopenharmony_ci hwdep->ops = ops; 27162306a36Sopenharmony_ci hwdep->private_data = tscm; 27262306a36Sopenharmony_ci hwdep->exclusive = true; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci tscm->hwdep = hwdep; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci return err; 27762306a36Sopenharmony_ci} 278