1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * dice_hwdep.c - a part of driver for DICE based devices
4 *
5 * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
6 * Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp>
7 */
8
9#include "dice.h"
10
11static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf,
12			    long count, loff_t *offset)
13{
14	struct snd_dice *dice = hwdep->private_data;
15	DEFINE_WAIT(wait);
16	union snd_firewire_event event;
17
18	spin_lock_irq(&dice->lock);
19
20	while (!dice->dev_lock_changed && dice->notification_bits == 0) {
21		prepare_to_wait(&dice->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
22		spin_unlock_irq(&dice->lock);
23		schedule();
24		finish_wait(&dice->hwdep_wait, &wait);
25		if (signal_pending(current))
26			return -ERESTARTSYS;
27		spin_lock_irq(&dice->lock);
28	}
29
30	memset(&event, 0, sizeof(event));
31	if (dice->dev_lock_changed) {
32		event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
33		event.lock_status.status = dice->dev_lock_count > 0;
34		dice->dev_lock_changed = false;
35
36		count = min_t(long, count, sizeof(event.lock_status));
37	} else {
38		event.dice_notification.type =
39					SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION;
40		event.dice_notification.notification = dice->notification_bits;
41		dice->notification_bits = 0;
42
43		count = min_t(long, count, sizeof(event.dice_notification));
44	}
45
46	spin_unlock_irq(&dice->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_dice *dice = hwdep->private_data;
58	__poll_t events;
59
60	poll_wait(file, &dice->hwdep_wait, wait);
61
62	spin_lock_irq(&dice->lock);
63	if (dice->dev_lock_changed || dice->notification_bits != 0)
64		events = EPOLLIN | EPOLLRDNORM;
65	else
66		events = 0;
67	spin_unlock_irq(&dice->lock);
68
69	return events;
70}
71
72static int hwdep_get_info(struct snd_dice *dice, void __user *arg)
73{
74	struct fw_device *dev = fw_parent_device(dice->unit);
75	struct snd_firewire_get_info info;
76
77	memset(&info, 0, sizeof(info));
78	info.type = SNDRV_FIREWIRE_TYPE_DICE;
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	strscpy(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_dice *dice)
92{
93	int err;
94
95	spin_lock_irq(&dice->lock);
96
97	if (dice->dev_lock_count == 0) {
98		dice->dev_lock_count = -1;
99		err = 0;
100	} else {
101		err = -EBUSY;
102	}
103
104	spin_unlock_irq(&dice->lock);
105
106	return err;
107}
108
109static int hwdep_unlock(struct snd_dice *dice)
110{
111	int err;
112
113	spin_lock_irq(&dice->lock);
114
115	if (dice->dev_lock_count == -1) {
116		dice->dev_lock_count = 0;
117		err = 0;
118	} else {
119		err = -EBADFD;
120	}
121
122	spin_unlock_irq(&dice->lock);
123
124	return err;
125}
126
127static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
128{
129	struct snd_dice *dice = hwdep->private_data;
130
131	spin_lock_irq(&dice->lock);
132	if (dice->dev_lock_count == -1)
133		dice->dev_lock_count = 0;
134	spin_unlock_irq(&dice->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_dice *dice = hwdep->private_data;
143
144	switch (cmd) {
145	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
146		return hwdep_get_info(dice, (void __user *)arg);
147	case SNDRV_FIREWIRE_IOCTL_LOCK:
148		return hwdep_lock(dice);
149	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
150		return hwdep_unlock(dice);
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_dice_create_hwdep(struct snd_dice *dice)
168{
169	static const struct snd_hwdep_ops 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(dice->card, "DICE", 0, &hwdep);
180	if (err < 0)
181		return err;
182	strcpy(hwdep->name, "DICE");
183	hwdep->iface = SNDRV_HWDEP_IFACE_FW_DICE;
184	hwdep->ops = ops;
185	hwdep->private_data = dice;
186	hwdep->exclusive = true;
187
188	return 0;
189}
190