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 | |
19 | static 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(lock: &dg00x->lock); |
27 | |
28 | while (!dg00x->dev_lock_changed && dg00x->msg == 0) { |
29 | prepare_to_wait(wq_head: &dg00x->hwdep_wait, wq_entry: &wait, TASK_INTERRUPTIBLE); |
30 | spin_unlock_irq(lock: &dg00x->lock); |
31 | schedule(); |
32 | finish_wait(wq_head: &dg00x->hwdep_wait, wq_entry: &wait); |
33 | if (signal_pending(current)) |
34 | return -ERESTARTSYS; |
35 | spin_lock_irq(lock: &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(lock: &dg00x->lock); |
55 | |
56 | if (copy_to_user(to: buf, from: &event, n: count)) |
57 | return -EFAULT; |
58 | |
59 | return count; |
60 | } |
61 | |
62 | static __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(filp: file, wait_address: &dg00x->hwdep_wait, p: wait); |
69 | |
70 | spin_lock_irq(lock: &dg00x->lock); |
71 | if (dg00x->dev_lock_changed || dg00x->msg) |
72 | events = EPOLLIN | EPOLLRDNORM; |
73 | else |
74 | events = 0; |
75 | spin_unlock_irq(lock: &dg00x->lock); |
76 | |
77 | return events; |
78 | } |
79 | |
80 | static 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(to: arg, from: &info, n: sizeof(info))) |
94 | return -EFAULT; |
95 | |
96 | return 0; |
97 | } |
98 | |
99 | static int hwdep_lock(struct snd_dg00x *dg00x) |
100 | { |
101 | int err; |
102 | |
103 | spin_lock_irq(lock: &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(lock: &dg00x->lock); |
113 | |
114 | return err; |
115 | } |
116 | |
117 | static int hwdep_unlock(struct snd_dg00x *dg00x) |
118 | { |
119 | int err; |
120 | |
121 | spin_lock_irq(lock: &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(lock: &dg00x->lock); |
131 | |
132 | return err; |
133 | } |
134 | |
135 | static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) |
136 | { |
137 | struct snd_dg00x *dg00x = hwdep->private_data; |
138 | |
139 | spin_lock_irq(lock: &dg00x->lock); |
140 | if (dg00x->dev_lock_count == -1) |
141 | dg00x->dev_lock_count = 0; |
142 | spin_unlock_irq(lock: &dg00x->lock); |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | static 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, arg: (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 |
165 | static 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 | arg: (unsigned long)compat_ptr(uptr: arg)); |
170 | } |
171 | #else |
172 | #define hwdep_compat_ioctl NULL |
173 | #endif |
174 | |
175 | int 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(card: dg00x->card, id: "Digi00x" , device: 0, rhwdep: &hwdep); |
188 | if (err < 0) |
189 | return err; |
190 | |
191 | strcpy(p: hwdep->name, q: "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 | |