162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * motu-hwdep.c - a part of driver for MOTU FireWire series
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci/*
962306a36Sopenharmony_ci * This codes have five functionalities.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * 1.get information about firewire node
1262306a36Sopenharmony_ci * 2.get notification about starting/stopping stream
1362306a36Sopenharmony_ci * 3.lock/unlock streaming
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include "motu.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistatic bool has_dsp_event(struct snd_motu *motu)
2062306a36Sopenharmony_ci{
2162306a36Sopenharmony_ci	if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP)
2262306a36Sopenharmony_ci		return (snd_motu_register_dsp_message_parser_count_event(motu) > 0);
2362306a36Sopenharmony_ci	else
2462306a36Sopenharmony_ci		return false;
2562306a36Sopenharmony_ci}
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
2862306a36Sopenharmony_ci		       loff_t *offset)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	struct snd_motu *motu = hwdep->private_data;
3162306a36Sopenharmony_ci	DEFINE_WAIT(wait);
3262306a36Sopenharmony_ci	union snd_firewire_event event;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	spin_lock_irq(&motu->lock);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	while (!motu->dev_lock_changed && motu->msg == 0 && !has_dsp_event(motu)) {
3762306a36Sopenharmony_ci		prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
3862306a36Sopenharmony_ci		spin_unlock_irq(&motu->lock);
3962306a36Sopenharmony_ci		schedule();
4062306a36Sopenharmony_ci		finish_wait(&motu->hwdep_wait, &wait);
4162306a36Sopenharmony_ci		if (signal_pending(current))
4262306a36Sopenharmony_ci			return -ERESTARTSYS;
4362306a36Sopenharmony_ci		spin_lock_irq(&motu->lock);
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	memset(&event, 0, sizeof(event));
4762306a36Sopenharmony_ci	if (motu->dev_lock_changed) {
4862306a36Sopenharmony_ci		event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
4962306a36Sopenharmony_ci		event.lock_status.status = (motu->dev_lock_count > 0);
5062306a36Sopenharmony_ci		motu->dev_lock_changed = false;
5162306a36Sopenharmony_ci		spin_unlock_irq(&motu->lock);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci		count = min_t(long, count, sizeof(event));
5462306a36Sopenharmony_ci		if (copy_to_user(buf, &event, count))
5562306a36Sopenharmony_ci			return -EFAULT;
5662306a36Sopenharmony_ci	} else if (motu->msg > 0) {
5762306a36Sopenharmony_ci		event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION;
5862306a36Sopenharmony_ci		event.motu_notification.message = motu->msg;
5962306a36Sopenharmony_ci		motu->msg = 0;
6062306a36Sopenharmony_ci		spin_unlock_irq(&motu->lock);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci		count = min_t(long, count, sizeof(event));
6362306a36Sopenharmony_ci		if (copy_to_user(buf, &event, count))
6462306a36Sopenharmony_ci			return -EFAULT;
6562306a36Sopenharmony_ci	} else if (has_dsp_event(motu)) {
6662306a36Sopenharmony_ci		size_t consumed = 0;
6762306a36Sopenharmony_ci		u32 __user *ptr;
6862306a36Sopenharmony_ci		u32 ev;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci		spin_unlock_irq(&motu->lock);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci		// Header is filled later.
7362306a36Sopenharmony_ci		consumed += sizeof(event.motu_register_dsp_change);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci		while (consumed < count &&
7662306a36Sopenharmony_ci		       snd_motu_register_dsp_message_parser_copy_event(motu, &ev)) {
7762306a36Sopenharmony_ci			ptr = (u32 __user *)(buf + consumed);
7862306a36Sopenharmony_ci			if (put_user(ev, ptr))
7962306a36Sopenharmony_ci				return -EFAULT;
8062306a36Sopenharmony_ci			consumed += sizeof(ev);
8162306a36Sopenharmony_ci		}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci		event.motu_register_dsp_change.type = SNDRV_FIREWIRE_EVENT_MOTU_REGISTER_DSP_CHANGE;
8462306a36Sopenharmony_ci		event.motu_register_dsp_change.count =
8562306a36Sopenharmony_ci			(consumed - sizeof(event.motu_register_dsp_change)) / 4;
8662306a36Sopenharmony_ci		if (copy_to_user(buf, &event, sizeof(event.motu_register_dsp_change)))
8762306a36Sopenharmony_ci			return -EFAULT;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		count = consumed;
9062306a36Sopenharmony_ci	} else {
9162306a36Sopenharmony_ci		spin_unlock_irq(&motu->lock);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		count = 0;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	return count;
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_cistatic __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
10062306a36Sopenharmony_ci			       poll_table *wait)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct snd_motu *motu = hwdep->private_data;
10362306a36Sopenharmony_ci	__poll_t events;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	poll_wait(file, &motu->hwdep_wait, wait);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	spin_lock_irq(&motu->lock);
10862306a36Sopenharmony_ci	if (motu->dev_lock_changed || motu->msg || has_dsp_event(motu))
10962306a36Sopenharmony_ci		events = EPOLLIN | EPOLLRDNORM;
11062306a36Sopenharmony_ci	else
11162306a36Sopenharmony_ci		events = 0;
11262306a36Sopenharmony_ci	spin_unlock_irq(&motu->lock);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	return events | EPOLLOUT;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic int hwdep_get_info(struct snd_motu *motu, void __user *arg)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct fw_device *dev = fw_parent_device(motu->unit);
12062306a36Sopenharmony_ci	struct snd_firewire_get_info info;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	memset(&info, 0, sizeof(info));
12362306a36Sopenharmony_ci	info.type = SNDRV_FIREWIRE_TYPE_MOTU;
12462306a36Sopenharmony_ci	info.card = dev->card->index;
12562306a36Sopenharmony_ci	*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
12662306a36Sopenharmony_ci	*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
12762306a36Sopenharmony_ci	strscpy(info.device_name, dev_name(&dev->device),
12862306a36Sopenharmony_ci		sizeof(info.device_name));
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	if (copy_to_user(arg, &info, sizeof(info)))
13162306a36Sopenharmony_ci		return -EFAULT;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	return 0;
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic int hwdep_lock(struct snd_motu *motu)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	int err;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	spin_lock_irq(&motu->lock);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (motu->dev_lock_count == 0) {
14362306a36Sopenharmony_ci		motu->dev_lock_count = -1;
14462306a36Sopenharmony_ci		err = 0;
14562306a36Sopenharmony_ci	} else {
14662306a36Sopenharmony_ci		err = -EBUSY;
14762306a36Sopenharmony_ci	}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	spin_unlock_irq(&motu->lock);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return err;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic int hwdep_unlock(struct snd_motu *motu)
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	int err;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	spin_lock_irq(&motu->lock);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	if (motu->dev_lock_count == -1) {
16162306a36Sopenharmony_ci		motu->dev_lock_count = 0;
16262306a36Sopenharmony_ci		err = 0;
16362306a36Sopenharmony_ci	} else {
16462306a36Sopenharmony_ci		err = -EBADFD;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	spin_unlock_irq(&motu->lock);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	return err;
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cistatic int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	struct snd_motu *motu = hwdep->private_data;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	spin_lock_irq(&motu->lock);
17762306a36Sopenharmony_ci	if (motu->dev_lock_count == -1)
17862306a36Sopenharmony_ci		motu->dev_lock_count = 0;
17962306a36Sopenharmony_ci	spin_unlock_irq(&motu->lock);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	return 0;
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cistatic int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
18562306a36Sopenharmony_ci	    unsigned int cmd, unsigned long arg)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	struct snd_motu *motu = hwdep->private_data;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	switch (cmd) {
19062306a36Sopenharmony_ci	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
19162306a36Sopenharmony_ci		return hwdep_get_info(motu, (void __user *)arg);
19262306a36Sopenharmony_ci	case SNDRV_FIREWIRE_IOCTL_LOCK:
19362306a36Sopenharmony_ci		return hwdep_lock(motu);
19462306a36Sopenharmony_ci	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
19562306a36Sopenharmony_ci		return hwdep_unlock(motu);
19662306a36Sopenharmony_ci	case SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_METER:
19762306a36Sopenharmony_ci	{
19862306a36Sopenharmony_ci		struct snd_firewire_motu_register_dsp_meter *meter;
19962306a36Sopenharmony_ci		int err;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci		if (!(motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP))
20262306a36Sopenharmony_ci			return -ENXIO;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci		meter = kzalloc(sizeof(*meter), GFP_KERNEL);
20562306a36Sopenharmony_ci		if (!meter)
20662306a36Sopenharmony_ci			return -ENOMEM;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci		snd_motu_register_dsp_message_parser_copy_meter(motu, meter);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci		err = copy_to_user((void __user *)arg, meter, sizeof(*meter));
21162306a36Sopenharmony_ci		kfree(meter);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci		if (err)
21462306a36Sopenharmony_ci			return -EFAULT;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci		return 0;
21762306a36Sopenharmony_ci	}
21862306a36Sopenharmony_ci	case SNDRV_FIREWIRE_IOCTL_MOTU_COMMAND_DSP_METER:
21962306a36Sopenharmony_ci	{
22062306a36Sopenharmony_ci		struct snd_firewire_motu_command_dsp_meter *meter;
22162306a36Sopenharmony_ci		int err;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci		if (!(motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP))
22462306a36Sopenharmony_ci			return -ENXIO;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci		meter = kzalloc(sizeof(*meter), GFP_KERNEL);
22762306a36Sopenharmony_ci		if (!meter)
22862306a36Sopenharmony_ci			return -ENOMEM;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci		snd_motu_command_dsp_message_parser_copy_meter(motu, meter);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci		err = copy_to_user((void __user *)arg, meter, sizeof(*meter));
23362306a36Sopenharmony_ci		kfree(meter);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci		if (err)
23662306a36Sopenharmony_ci			return -EFAULT;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci		return 0;
23962306a36Sopenharmony_ci	}
24062306a36Sopenharmony_ci	case SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_PARAMETER:
24162306a36Sopenharmony_ci	{
24262306a36Sopenharmony_ci		struct snd_firewire_motu_register_dsp_parameter *param;
24362306a36Sopenharmony_ci		int err;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci		if (!(motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP))
24662306a36Sopenharmony_ci			return -ENXIO;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci		param = kzalloc(sizeof(*param), GFP_KERNEL);
24962306a36Sopenharmony_ci		if (!param)
25062306a36Sopenharmony_ci			return -ENOMEM;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci		snd_motu_register_dsp_message_parser_copy_parameter(motu, param);
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci		err = copy_to_user((void __user *)arg, param, sizeof(*param));
25562306a36Sopenharmony_ci		kfree(param);
25662306a36Sopenharmony_ci		if (err)
25762306a36Sopenharmony_ci			return -EFAULT;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci		return 0;
26062306a36Sopenharmony_ci	}
26162306a36Sopenharmony_ci	default:
26262306a36Sopenharmony_ci		return -ENOIOCTLCMD;
26362306a36Sopenharmony_ci	}
26462306a36Sopenharmony_ci}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci#ifdef CONFIG_COMPAT
26762306a36Sopenharmony_cistatic int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
26862306a36Sopenharmony_ci			      unsigned int cmd, unsigned long arg)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	return hwdep_ioctl(hwdep, file, cmd,
27162306a36Sopenharmony_ci			   (unsigned long)compat_ptr(arg));
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci#else
27462306a36Sopenharmony_ci#define hwdep_compat_ioctl NULL
27562306a36Sopenharmony_ci#endif
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ciint snd_motu_create_hwdep_device(struct snd_motu *motu)
27862306a36Sopenharmony_ci{
27962306a36Sopenharmony_ci	static const struct snd_hwdep_ops ops = {
28062306a36Sopenharmony_ci		.read		= hwdep_read,
28162306a36Sopenharmony_ci		.release	= hwdep_release,
28262306a36Sopenharmony_ci		.poll		= hwdep_poll,
28362306a36Sopenharmony_ci		.ioctl		= hwdep_ioctl,
28462306a36Sopenharmony_ci		.ioctl_compat	= hwdep_compat_ioctl,
28562306a36Sopenharmony_ci	};
28662306a36Sopenharmony_ci	struct snd_hwdep *hwdep;
28762306a36Sopenharmony_ci	int err;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	err = snd_hwdep_new(motu->card, motu->card->driver, 0, &hwdep);
29062306a36Sopenharmony_ci	if (err < 0)
29162306a36Sopenharmony_ci		return err;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	strcpy(hwdep->name, "MOTU");
29462306a36Sopenharmony_ci	hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU;
29562306a36Sopenharmony_ci	hwdep->ops = ops;
29662306a36Sopenharmony_ci	hwdep->private_data = motu;
29762306a36Sopenharmony_ci	hwdep->exclusive = true;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	motu->hwdep = hwdep;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	return 0;
30262306a36Sopenharmony_ci}
303