1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * digi00x-hwdep.c - a part of driver for Digidesign Digi 002/003 family
4 *
5 * Copyright (c) 2014-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 * 4.get asynchronous messaging
15 */
16
17#include "digi00x.h"
18
19static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count,
20		       loff_t *offset)
21{
22	struct snd_dg00x *dg00x = hwdep->private_data;
23	DEFINE_WAIT(wait);
24	union snd_firewire_event event;
25
26	spin_lock_irq(&dg00x->lock);
27
28	while (!dg00x->dev_lock_changed && dg00x->msg == 0) {
29		prepare_to_wait(&dg00x->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
30		spin_unlock_irq(&dg00x->lock);
31		schedule();
32		finish_wait(&dg00x->hwdep_wait, &wait);
33		if (signal_pending(current))
34			return -ERESTARTSYS;
35		spin_lock_irq(&dg00x->lock);
36	}
37
38	memset(&event, 0, sizeof(event));
39	if (dg00x->dev_lock_changed) {
40		event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
41		event.lock_status.status = (dg00x->dev_lock_count > 0);
42		dg00x->dev_lock_changed = false;
43
44		count = min_t(long, count, sizeof(event.lock_status));
45	} else {
46		event.digi00x_message.type =
47					SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE;
48		event.digi00x_message.message = dg00x->msg;
49		dg00x->msg = 0;
50
51		count = min_t(long, count, sizeof(event.digi00x_message));
52	}
53
54	spin_unlock_irq(&dg00x->lock);
55
56	if (copy_to_user(buf, &event, count))
57		return -EFAULT;
58
59	return count;
60}
61
62static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
63			       poll_table *wait)
64{
65	struct snd_dg00x *dg00x = hwdep->private_data;
66	__poll_t events;
67
68	poll_wait(file, &dg00x->hwdep_wait, wait);
69
70	spin_lock_irq(&dg00x->lock);
71	if (dg00x->dev_lock_changed || dg00x->msg)
72		events = EPOLLIN | EPOLLRDNORM;
73	else
74		events = 0;
75	spin_unlock_irq(&dg00x->lock);
76
77	return events;
78}
79
80static int hwdep_get_info(struct snd_dg00x *dg00x, void __user *arg)
81{
82	struct fw_device *dev = fw_parent_device(dg00x->unit);
83	struct snd_firewire_get_info info;
84
85	memset(&info, 0, sizeof(info));
86	info.type = SNDRV_FIREWIRE_TYPE_DIGI00X;
87	info.card = dev->card->index;
88	*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
89	*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
90	strscpy(info.device_name, dev_name(&dev->device),
91		sizeof(info.device_name));
92
93	if (copy_to_user(arg, &info, sizeof(info)))
94		return -EFAULT;
95
96	return 0;
97}
98
99static int hwdep_lock(struct snd_dg00x *dg00x)
100{
101	int err;
102
103	spin_lock_irq(&dg00x->lock);
104
105	if (dg00x->dev_lock_count == 0) {
106		dg00x->dev_lock_count = -1;
107		err = 0;
108	} else {
109		err = -EBUSY;
110	}
111
112	spin_unlock_irq(&dg00x->lock);
113
114	return err;
115}
116
117static int hwdep_unlock(struct snd_dg00x *dg00x)
118{
119	int err;
120
121	spin_lock_irq(&dg00x->lock);
122
123	if (dg00x->dev_lock_count == -1) {
124		dg00x->dev_lock_count = 0;
125		err = 0;
126	} else {
127		err = -EBADFD;
128	}
129
130	spin_unlock_irq(&dg00x->lock);
131
132	return err;
133}
134
135static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
136{
137	struct snd_dg00x *dg00x = hwdep->private_data;
138
139	spin_lock_irq(&dg00x->lock);
140	if (dg00x->dev_lock_count == -1)
141		dg00x->dev_lock_count = 0;
142	spin_unlock_irq(&dg00x->lock);
143
144	return 0;
145}
146
147static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
148	    unsigned int cmd, unsigned long arg)
149{
150	struct snd_dg00x *dg00x = hwdep->private_data;
151
152	switch (cmd) {
153	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
154		return hwdep_get_info(dg00x, (void __user *)arg);
155	case SNDRV_FIREWIRE_IOCTL_LOCK:
156		return hwdep_lock(dg00x);
157	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
158		return hwdep_unlock(dg00x);
159	default:
160		return -ENOIOCTLCMD;
161	}
162}
163
164#ifdef CONFIG_COMPAT
165static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
166			      unsigned int cmd, unsigned long arg)
167{
168	return hwdep_ioctl(hwdep, file, cmd,
169			   (unsigned long)compat_ptr(arg));
170}
171#else
172#define hwdep_compat_ioctl NULL
173#endif
174
175int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x)
176{
177	static const struct snd_hwdep_ops ops = {
178		.read		= hwdep_read,
179		.release	= hwdep_release,
180		.poll		= hwdep_poll,
181		.ioctl		= hwdep_ioctl,
182		.ioctl_compat	= hwdep_compat_ioctl,
183	};
184	struct snd_hwdep *hwdep;
185	int err;
186
187	err = snd_hwdep_new(dg00x->card, "Digi00x", 0, &hwdep);
188	if (err < 0)
189		return err;
190
191	strcpy(hwdep->name, "Digi00x");
192	hwdep->iface = SNDRV_HWDEP_IFACE_FW_DIGI00X;
193	hwdep->ops = ops;
194	hwdep->private_data = dg00x;
195	hwdep->exclusive = true;
196
197	return err;
198}
199