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