cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

tascam-hwdep.c (6214B)


      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}