Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * tascam-hwdep.c - a part of driver for TASCAM FireWire series
0004  *
0005  * Copyright (c) 2015 Takashi Sakamoto
0006  */
0007 
0008 /*
0009  * This codes give three functionality.
0010  *
0011  * 1.get firewire node information
0012  * 2.get notification about starting/stopping stream
0013  * 3.lock/unlock stream
0014  */
0015 
0016 #include "tascam.h"
0017 
0018 static long tscm_hwdep_read_locked(struct snd_tscm *tscm, char __user *buf,
0019                    long count, loff_t *offset)
0020     __releases(&tscm->lock)
0021 {
0022     struct snd_firewire_event_lock_status event = {
0023         .type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,
0024     };
0025 
0026     event.status = (tscm->dev_lock_count > 0);
0027     tscm->dev_lock_changed = false;
0028     count = min_t(long, count, sizeof(event));
0029 
0030     spin_unlock_irq(&tscm->lock);
0031 
0032     if (copy_to_user(buf, &event, count))
0033         return -EFAULT;
0034 
0035     return count;
0036 }
0037 
0038 static long tscm_hwdep_read_queue(struct snd_tscm *tscm, char __user *buf,
0039                   long remained, loff_t *offset)
0040     __releases(&tscm->lock)
0041 {
0042     char __user *pos = buf;
0043     unsigned int type = SNDRV_FIREWIRE_EVENT_TASCAM_CONTROL;
0044     struct snd_firewire_tascam_change *entries = tscm->queue;
0045     long count;
0046 
0047     // At least, one control event can be copied.
0048     if (remained < sizeof(type) + sizeof(*entries)) {
0049         spin_unlock_irq(&tscm->lock);
0050         return -EINVAL;
0051     }
0052 
0053     // Copy the type field later.
0054     count = sizeof(type);
0055     remained -= sizeof(type);
0056     pos += sizeof(type);
0057 
0058     while (true) {
0059         unsigned int head_pos;
0060         unsigned int tail_pos;
0061         unsigned int length;
0062 
0063         if (tscm->pull_pos == tscm->push_pos)
0064             break;
0065         else if (tscm->pull_pos < tscm->push_pos)
0066             tail_pos = tscm->push_pos;
0067         else
0068             tail_pos = SND_TSCM_QUEUE_COUNT;
0069         head_pos = tscm->pull_pos;
0070 
0071         length = (tail_pos - head_pos) * sizeof(*entries);
0072         if (remained < length)
0073             length = rounddown(remained, sizeof(*entries));
0074         if (length == 0)
0075             break;
0076 
0077         spin_unlock_irq(&tscm->lock);
0078         if (copy_to_user(pos, &entries[head_pos], length))
0079             return -EFAULT;
0080 
0081         spin_lock_irq(&tscm->lock);
0082 
0083         tscm->pull_pos = tail_pos % SND_TSCM_QUEUE_COUNT;
0084 
0085         count += length;
0086         remained -= length;
0087         pos += length;
0088     }
0089 
0090     spin_unlock_irq(&tscm->lock);
0091 
0092     if (copy_to_user(buf, &type, sizeof(type)))
0093         return -EFAULT;
0094 
0095     return count;
0096 }
0097 
0098 static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
0099                loff_t *offset)
0100 {
0101     struct snd_tscm *tscm = hwdep->private_data;
0102     DEFINE_WAIT(wait);
0103 
0104     spin_lock_irq(&tscm->lock);
0105 
0106     while (!tscm->dev_lock_changed && tscm->push_pos == tscm->pull_pos) {
0107         prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
0108         spin_unlock_irq(&tscm->lock);
0109         schedule();
0110         finish_wait(&tscm->hwdep_wait, &wait);
0111         if (signal_pending(current))
0112             return -ERESTARTSYS;
0113         spin_lock_irq(&tscm->lock);
0114     }
0115 
0116     // NOTE: The acquired lock should be released in callee side.
0117     if (tscm->dev_lock_changed) {
0118         count = tscm_hwdep_read_locked(tscm, buf, count, offset);
0119     } else if (tscm->push_pos != tscm->pull_pos) {
0120         count = tscm_hwdep_read_queue(tscm, buf, count, offset);
0121     } else {
0122         spin_unlock_irq(&tscm->lock);
0123         count = 0;
0124     }
0125 
0126     return count;
0127 }
0128 
0129 static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
0130                    poll_table *wait)
0131 {
0132     struct snd_tscm *tscm = hwdep->private_data;
0133     __poll_t events;
0134 
0135     poll_wait(file, &tscm->hwdep_wait, wait);
0136 
0137     spin_lock_irq(&tscm->lock);
0138     if (tscm->dev_lock_changed || tscm->push_pos != tscm->pull_pos)
0139         events = EPOLLIN | EPOLLRDNORM;
0140     else
0141         events = 0;
0142     spin_unlock_irq(&tscm->lock);
0143 
0144     return events;
0145 }
0146 
0147 static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg)
0148 {
0149     struct fw_device *dev = fw_parent_device(tscm->unit);
0150     struct snd_firewire_get_info info;
0151 
0152     memset(&info, 0, sizeof(info));
0153     info.type = SNDRV_FIREWIRE_TYPE_TASCAM;
0154     info.card = dev->card->index;
0155     *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
0156     *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
0157     strscpy(info.device_name, dev_name(&dev->device),
0158         sizeof(info.device_name));
0159 
0160     if (copy_to_user(arg, &info, sizeof(info)))
0161         return -EFAULT;
0162 
0163     return 0;
0164 }
0165 
0166 static int hwdep_lock(struct snd_tscm *tscm)
0167 {
0168     int err;
0169 
0170     spin_lock_irq(&tscm->lock);
0171 
0172     if (tscm->dev_lock_count == 0) {
0173         tscm->dev_lock_count = -1;
0174         err = 0;
0175     } else {
0176         err = -EBUSY;
0177     }
0178 
0179     spin_unlock_irq(&tscm->lock);
0180 
0181     return err;
0182 }
0183 
0184 static int hwdep_unlock(struct snd_tscm *tscm)
0185 {
0186     int err;
0187 
0188     spin_lock_irq(&tscm->lock);
0189 
0190     if (tscm->dev_lock_count == -1) {
0191         tscm->dev_lock_count = 0;
0192         err = 0;
0193     } else {
0194         err = -EBADFD;
0195     }
0196 
0197     spin_unlock_irq(&tscm->lock);
0198 
0199     return err;
0200 }
0201 
0202 static int tscm_hwdep_state(struct snd_tscm *tscm, void __user *arg)
0203 {
0204     if (copy_to_user(arg, tscm->state, sizeof(tscm->state)))
0205         return -EFAULT;
0206 
0207     return 0;
0208 }
0209 
0210 static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
0211 {
0212     struct snd_tscm *tscm = hwdep->private_data;
0213 
0214     spin_lock_irq(&tscm->lock);
0215     if (tscm->dev_lock_count == -1)
0216         tscm->dev_lock_count = 0;
0217     spin_unlock_irq(&tscm->lock);
0218 
0219     return 0;
0220 }
0221 
0222 static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
0223         unsigned int cmd, unsigned long arg)
0224 {
0225     struct snd_tscm *tscm = hwdep->private_data;
0226 
0227     switch (cmd) {
0228     case SNDRV_FIREWIRE_IOCTL_GET_INFO:
0229         return hwdep_get_info(tscm, (void __user *)arg);
0230     case SNDRV_FIREWIRE_IOCTL_LOCK:
0231         return hwdep_lock(tscm);
0232     case SNDRV_FIREWIRE_IOCTL_UNLOCK:
0233         return hwdep_unlock(tscm);
0234     case SNDRV_FIREWIRE_IOCTL_TASCAM_STATE:
0235         return tscm_hwdep_state(tscm, (void __user *)arg);
0236     default:
0237         return -ENOIOCTLCMD;
0238     }
0239 }
0240 
0241 #ifdef CONFIG_COMPAT
0242 static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
0243                   unsigned int cmd, unsigned long arg)
0244 {
0245     return hwdep_ioctl(hwdep, file, cmd,
0246                (unsigned long)compat_ptr(arg));
0247 }
0248 #else
0249 #define hwdep_compat_ioctl NULL
0250 #endif
0251 
0252 int snd_tscm_create_hwdep_device(struct snd_tscm *tscm)
0253 {
0254     static const struct snd_hwdep_ops ops = {
0255         .read       = hwdep_read,
0256         .release    = hwdep_release,
0257         .poll       = hwdep_poll,
0258         .ioctl      = hwdep_ioctl,
0259         .ioctl_compat   = hwdep_compat_ioctl,
0260     };
0261     struct snd_hwdep *hwdep;
0262     int err;
0263 
0264     err = snd_hwdep_new(tscm->card, "Tascam", 0, &hwdep);
0265     if (err < 0)
0266         return err;
0267 
0268     strcpy(hwdep->name, "Tascam");
0269     hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM;
0270     hwdep->ops = ops;
0271     hwdep->private_data = tscm;
0272     hwdep->exclusive = true;
0273 
0274     tscm->hwdep = hwdep;
0275 
0276     return err;
0277 }