Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 LT |
2 | /* |
3 | * Misc and compatibility things | |
c1017a4c | 4 | * Copyright (c) by Jaroslav Kysela <perex@perex.cz> |
1da177e4 LT |
5 | */ |
6 | ||
1da177e4 | 7 | #include <linux/init.h> |
d81a6d71 | 8 | #include <linux/export.h> |
31623caa | 9 | #include <linux/moduleparam.h> |
1da177e4 | 10 | #include <linux/time.h> |
5a0e3ad6 | 11 | #include <linux/slab.h> |
b1d5776d | 12 | #include <linux/ioport.h> |
ef34a0ae | 13 | #include <linux/fs.h> |
1da177e4 LT |
14 | #include <sound/core.h> |
15 | ||
36ce99c1 TI |
16 | #ifdef CONFIG_SND_DEBUG |
17 | ||
18 | #ifdef CONFIG_SND_DEBUG_VERBOSE | |
19 | #define DEFAULT_DEBUG_LEVEL 2 | |
20 | #else | |
21 | #define DEFAULT_DEBUG_LEVEL 1 | |
22 | #endif | |
23 | ||
24 | static int debug = DEFAULT_DEBUG_LEVEL; | |
25 | module_param(debug, int, 0644); | |
26 | MODULE_PARM_DESC(debug, "Debug level (0 = disable)"); | |
27 | ||
28 | #endif /* CONFIG_SND_DEBUG */ | |
29 | ||
b1d5776d TI |
30 | void release_and_free_resource(struct resource *res) |
31 | { | |
32 | if (res) { | |
33 | release_resource(res); | |
e38e0cfa | 34 | kfree(res); |
b1d5776d TI |
35 | } |
36 | } | |
c0d3fb39 TI |
37 | EXPORT_SYMBOL(release_and_free_resource); |
38 | ||
1da177e4 | 39 | #ifdef CONFIG_SND_VERBOSE_PRINTK |
36ce99c1 | 40 | /* strip the leading path if the given path is absolute */ |
1b0053a0 | 41 | static const char *sanity_file_name(const char *path) |
1da177e4 | 42 | { |
1b0053a0 TI |
43 | if (*path == '/') |
44 | return strrchr(path, '/') + 1; | |
45 | else | |
46 | return path; | |
47 | } | |
1da177e4 LT |
48 | #endif |
49 | ||
36ce99c1 TI |
50 | #if defined(CONFIG_SND_DEBUG) || defined(CONFIG_SND_VERBOSE_PRINTK) |
51 | void __snd_printk(unsigned int level, const char *path, int line, | |
52 | const char *format, ...) | |
1da177e4 LT |
53 | { |
54 | va_list args; | |
890ee02a | 55 | #ifdef CONFIG_SND_VERBOSE_PRINTK |
b778b3f2 | 56 | int kern_level; |
890ee02a TI |
57 | struct va_format vaf; |
58 | char verbose_fmt[] = KERN_DEFAULT "ALSA %s:%d %pV"; | |
0a4824bf | 59 | bool level_found = false; |
890ee02a TI |
60 | #endif |
61 | ||
7913a499 | 62 | #ifdef CONFIG_SND_DEBUG |
36ce99c1 TI |
63 | if (debug < level) |
64 | return; | |
65 | #endif | |
890ee02a | 66 | |
1da177e4 | 67 | va_start(args, format); |
890ee02a TI |
68 | #ifdef CONFIG_SND_VERBOSE_PRINTK |
69 | vaf.fmt = format; | |
70 | vaf.va = &args; | |
b778b3f2 | 71 | |
0a4824bf PM |
72 | while ((kern_level = printk_get_level(vaf.fmt)) != 0) { |
73 | const char *end_of_header = printk_skip_level(vaf.fmt); | |
74 | ||
75 | /* Ignore KERN_CONT. We print filename:line for each piece. */ | |
76 | if (kern_level >= '0' && kern_level <= '7') { | |
77 | memcpy(verbose_fmt, vaf.fmt, end_of_header - vaf.fmt); | |
78 | level_found = true; | |
79 | } | |
80 | ||
b778b3f2 | 81 | vaf.fmt = end_of_header; |
0a4824bf PM |
82 | } |
83 | ||
84 | if (!level_found && level) | |
b778b3f2 | 85 | memcpy(verbose_fmt, KERN_DEBUG, sizeof(KERN_DEBUG) - 1); |
b778b3f2 | 86 | |
0a4824bf | 87 | printk(verbose_fmt, sanity_file_name(path), line, &vaf); |
890ee02a | 88 | #else |
1da177e4 | 89 | vprintk(format, args); |
890ee02a | 90 | #endif |
1da177e4 | 91 | va_end(args); |
1da177e4 | 92 | } |
36ce99c1 | 93 | EXPORT_SYMBOL_GPL(__snd_printk); |
1da177e4 | 94 | #endif |
d9ea472c TI |
95 | |
96 | #ifdef CONFIG_PCI | |
97 | #include <linux/pci.h> | |
98 | /** | |
d1458279 TI |
99 | * snd_pci_quirk_lookup_id - look up a PCI SSID quirk list |
100 | * @vendor: PCI SSV id | |
101 | * @device: PCI SSD id | |
d9ea472c TI |
102 | * @list: quirk list, terminated by a null entry |
103 | * | |
104 | * Look through the given quirk list and finds a matching entry | |
105 | * with the same PCI SSID. When subdevice is 0, all subdevice | |
106 | * values may match. | |
107 | * | |
108 | * Returns the matched entry pointer, or NULL if nothing matched. | |
109 | */ | |
110 | const struct snd_pci_quirk * | |
d1458279 TI |
111 | snd_pci_quirk_lookup_id(u16 vendor, u16 device, |
112 | const struct snd_pci_quirk *list) | |
d9ea472c TI |
113 | { |
114 | const struct snd_pci_quirk *q; | |
115 | ||
5576c4f2 | 116 | for (q = list; q->subvendor || q->subdevice; q++) { |
d1458279 | 117 | if (q->subvendor != vendor) |
8bd4bb7a TI |
118 | continue; |
119 | if (!q->subdevice || | |
d1458279 | 120 | (device & q->subdevice_mask) == q->subdevice) |
d9ea472c | 121 | return q; |
8bd4bb7a | 122 | } |
d9ea472c TI |
123 | return NULL; |
124 | } | |
d1458279 TI |
125 | EXPORT_SYMBOL(snd_pci_quirk_lookup_id); |
126 | ||
127 | /** | |
128 | * snd_pci_quirk_lookup - look up a PCI SSID quirk list | |
129 | * @pci: pci_dev handle | |
130 | * @list: quirk list, terminated by a null entry | |
131 | * | |
132 | * Look through the given quirk list and finds a matching entry | |
133 | * with the same PCI SSID. When subdevice is 0, all subdevice | |
134 | * values may match. | |
135 | * | |
136 | * Returns the matched entry pointer, or NULL if nothing matched. | |
137 | */ | |
138 | const struct snd_pci_quirk * | |
139 | snd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list) | |
140 | { | |
e5b50ada TI |
141 | if (!pci) |
142 | return NULL; | |
d1458279 TI |
143 | return snd_pci_quirk_lookup_id(pci->subsystem_vendor, |
144 | pci->subsystem_device, | |
145 | list); | |
146 | } | |
d9ea472c TI |
147 | EXPORT_SYMBOL(snd_pci_quirk_lookup); |
148 | #endif | |
ef34a0ae TI |
149 | |
150 | /* | |
151 | * Deferred async signal helpers | |
152 | * | |
153 | * Below are a few helper functions to wrap the async signal handling | |
154 | * in the deferred work. The main purpose is to avoid the messy deadlock | |
155 | * around tasklist_lock and co at the kill_fasync() invocation. | |
156 | * fasync_helper() and kill_fasync() are replaced with snd_fasync_helper() | |
157 | * and snd_kill_fasync(), respectively. In addition, snd_fasync_free() has | |
158 | * to be called at releasing the relevant file object. | |
159 | */ | |
160 | struct snd_fasync { | |
161 | struct fasync_struct *fasync; | |
162 | int signal; | |
163 | int poll; | |
164 | int on; | |
165 | struct list_head list; | |
166 | }; | |
167 | ||
168 | static DEFINE_SPINLOCK(snd_fasync_lock); | |
169 | static LIST_HEAD(snd_fasync_list); | |
170 | ||
171 | static void snd_fasync_work_fn(struct work_struct *work) | |
172 | { | |
173 | struct snd_fasync *fasync; | |
174 | ||
175 | spin_lock_irq(&snd_fasync_lock); | |
176 | while (!list_empty(&snd_fasync_list)) { | |
177 | fasync = list_first_entry(&snd_fasync_list, struct snd_fasync, list); | |
178 | list_del_init(&fasync->list); | |
179 | spin_unlock_irq(&snd_fasync_lock); | |
180 | if (fasync->on) | |
181 | kill_fasync(&fasync->fasync, fasync->signal, fasync->poll); | |
182 | spin_lock_irq(&snd_fasync_lock); | |
183 | } | |
184 | spin_unlock_irq(&snd_fasync_lock); | |
185 | } | |
186 | ||
187 | static DECLARE_WORK(snd_fasync_work, snd_fasync_work_fn); | |
188 | ||
189 | int snd_fasync_helper(int fd, struct file *file, int on, | |
190 | struct snd_fasync **fasyncp) | |
191 | { | |
192 | struct snd_fasync *fasync = NULL; | |
193 | ||
194 | if (on) { | |
195 | fasync = kzalloc(sizeof(*fasync), GFP_KERNEL); | |
196 | if (!fasync) | |
197 | return -ENOMEM; | |
198 | INIT_LIST_HEAD(&fasync->list); | |
199 | } | |
200 | ||
201 | spin_lock_irq(&snd_fasync_lock); | |
202 | if (*fasyncp) { | |
203 | kfree(fasync); | |
204 | fasync = *fasyncp; | |
205 | } else { | |
206 | if (!fasync) { | |
207 | spin_unlock_irq(&snd_fasync_lock); | |
208 | return 0; | |
209 | } | |
210 | *fasyncp = fasync; | |
211 | } | |
212 | fasync->on = on; | |
213 | spin_unlock_irq(&snd_fasync_lock); | |
214 | return fasync_helper(fd, file, on, &fasync->fasync); | |
215 | } | |
216 | EXPORT_SYMBOL_GPL(snd_fasync_helper); | |
217 | ||
218 | void snd_kill_fasync(struct snd_fasync *fasync, int signal, int poll) | |
219 | { | |
220 | unsigned long flags; | |
221 | ||
222 | if (!fasync || !fasync->on) | |
223 | return; | |
224 | spin_lock_irqsave(&snd_fasync_lock, flags); | |
225 | fasync->signal = signal; | |
226 | fasync->poll = poll; | |
227 | list_move(&fasync->list, &snd_fasync_list); | |
228 | schedule_work(&snd_fasync_work); | |
229 | spin_unlock_irqrestore(&snd_fasync_lock, flags); | |
230 | } | |
231 | EXPORT_SYMBOL_GPL(snd_kill_fasync); | |
232 | ||
233 | void snd_fasync_free(struct snd_fasync *fasync) | |
234 | { | |
235 | if (!fasync) | |
236 | return; | |
237 | fasync->on = 0; | |
238 | flush_work(&snd_fasync_work); | |
239 | kfree(fasync); | |
240 | } | |
241 | EXPORT_SYMBOL_GPL(snd_fasync_free); |