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