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