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 | |
11 | static 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(lock: &dice->lock); |
19 | |
20 | while (!dice->dev_lock_changed && dice->notification_bits == 0) { |
21 | prepare_to_wait(wq_head: &dice->hwdep_wait, wq_entry: &wait, TASK_INTERRUPTIBLE); |
22 | spin_unlock_irq(lock: &dice->lock); |
23 | schedule(); |
24 | finish_wait(wq_head: &dice->hwdep_wait, wq_entry: &wait); |
25 | if (signal_pending(current)) |
26 | return -ERESTARTSYS; |
27 | spin_lock_irq(lock: &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(lock: &dice->lock); |
47 | |
48 | if (copy_to_user(to: buf, from: &event, n: count)) |
49 | return -EFAULT; |
50 | |
51 | return count; |
52 | } |
53 | |
54 | static __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(filp: file, wait_address: &dice->hwdep_wait, p: wait); |
61 | |
62 | spin_lock_irq(lock: &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(lock: &dice->lock); |
68 | |
69 | return events; |
70 | } |
71 | |
72 | static 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(to: arg, from: &info, n: sizeof(info))) |
86 | return -EFAULT; |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static int hwdep_lock(struct snd_dice *dice) |
92 | { |
93 | int err; |
94 | |
95 | spin_lock_irq(lock: &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(lock: &dice->lock); |
105 | |
106 | return err; |
107 | } |
108 | |
109 | static int hwdep_unlock(struct snd_dice *dice) |
110 | { |
111 | int err; |
112 | |
113 | spin_lock_irq(lock: &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(lock: &dice->lock); |
123 | |
124 | return err; |
125 | } |
126 | |
127 | static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) |
128 | { |
129 | struct snd_dice *dice = hwdep->private_data; |
130 | |
131 | spin_lock_irq(lock: &dice->lock); |
132 | if (dice->dev_lock_count == -1) |
133 | dice->dev_lock_count = 0; |
134 | spin_unlock_irq(lock: &dice->lock); |
135 | |
136 | return 0; |
137 | } |
138 | |
139 | static 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, arg: (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 |
157 | static 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 | arg: (unsigned long)compat_ptr(uptr: arg)); |
162 | } |
163 | #else |
164 | #define hwdep_compat_ioctl NULL |
165 | #endif |
166 | |
167 | int 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(card: dice->card, id: "DICE" , device: 0, rhwdep: &hwdep); |
180 | if (err < 0) |
181 | return err; |
182 | strcpy(p: hwdep->name, q: "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 | |