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