18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Misc and compatibility things 48c2ecf20Sopenharmony_ci * Copyright (c) by Jaroslav Kysela <perex@perex.cz> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/init.h> 88c2ecf20Sopenharmony_ci#include <linux/export.h> 98c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 108c2ecf20Sopenharmony_ci#include <linux/time.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <linux/ioport.h> 138c2ecf20Sopenharmony_ci#include <linux/fs.h> 148c2ecf20Sopenharmony_ci#include <sound/core.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_DEBUG 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_DEBUG_VERBOSE 198c2ecf20Sopenharmony_ci#define DEFAULT_DEBUG_LEVEL 2 208c2ecf20Sopenharmony_ci#else 218c2ecf20Sopenharmony_ci#define DEFAULT_DEBUG_LEVEL 1 228c2ecf20Sopenharmony_ci#endif 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistatic int debug = DEFAULT_DEBUG_LEVEL; 258c2ecf20Sopenharmony_cimodule_param(debug, int, 0644); 268c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "Debug level (0 = disable)"); 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#endif /* CONFIG_SND_DEBUG */ 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_civoid release_and_free_resource(struct resource *res) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci if (res) { 338c2ecf20Sopenharmony_ci release_resource(res); 348c2ecf20Sopenharmony_ci kfree(res); 358c2ecf20Sopenharmony_ci } 368c2ecf20Sopenharmony_ci} 378c2ecf20Sopenharmony_ciEXPORT_SYMBOL(release_and_free_resource); 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_VERBOSE_PRINTK 408c2ecf20Sopenharmony_ci/* strip the leading path if the given path is absolute */ 418c2ecf20Sopenharmony_cistatic const char *sanity_file_name(const char *path) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci if (*path == '/') 448c2ecf20Sopenharmony_ci return strrchr(path, '/') + 1; 458c2ecf20Sopenharmony_ci else 468c2ecf20Sopenharmony_ci return path; 478c2ecf20Sopenharmony_ci} 488c2ecf20Sopenharmony_ci#endif 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci#if defined(CONFIG_SND_DEBUG) || defined(CONFIG_SND_VERBOSE_PRINTK) 518c2ecf20Sopenharmony_civoid __snd_printk(unsigned int level, const char *path, int line, 528c2ecf20Sopenharmony_ci const char *format, ...) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci va_list args; 558c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_VERBOSE_PRINTK 568c2ecf20Sopenharmony_ci int kern_level; 578c2ecf20Sopenharmony_ci struct va_format vaf; 588c2ecf20Sopenharmony_ci char verbose_fmt[] = KERN_DEFAULT "ALSA %s:%d %pV"; 598c2ecf20Sopenharmony_ci bool level_found = false; 608c2ecf20Sopenharmony_ci#endif 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_DEBUG 638c2ecf20Sopenharmony_ci if (debug < level) 648c2ecf20Sopenharmony_ci return; 658c2ecf20Sopenharmony_ci#endif 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci va_start(args, format); 688c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_VERBOSE_PRINTK 698c2ecf20Sopenharmony_ci vaf.fmt = format; 708c2ecf20Sopenharmony_ci vaf.va = &args; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci while ((kern_level = printk_get_level(vaf.fmt)) != 0) { 738c2ecf20Sopenharmony_ci const char *end_of_header = printk_skip_level(vaf.fmt); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci /* Ignore KERN_CONT. We print filename:line for each piece. */ 768c2ecf20Sopenharmony_ci if (kern_level >= '0' && kern_level <= '7') { 778c2ecf20Sopenharmony_ci memcpy(verbose_fmt, vaf.fmt, end_of_header - vaf.fmt); 788c2ecf20Sopenharmony_ci level_found = true; 798c2ecf20Sopenharmony_ci } 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci vaf.fmt = end_of_header; 828c2ecf20Sopenharmony_ci } 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci if (!level_found && level) 858c2ecf20Sopenharmony_ci memcpy(verbose_fmt, KERN_DEBUG, sizeof(KERN_DEBUG) - 1); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci printk(verbose_fmt, sanity_file_name(path), line, &vaf); 888c2ecf20Sopenharmony_ci#else 898c2ecf20Sopenharmony_ci vprintk(format, args); 908c2ecf20Sopenharmony_ci#endif 918c2ecf20Sopenharmony_ci va_end(args); 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(__snd_printk); 948c2ecf20Sopenharmony_ci#endif 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci#ifdef CONFIG_PCI 978c2ecf20Sopenharmony_ci#include <linux/pci.h> 988c2ecf20Sopenharmony_ci/** 998c2ecf20Sopenharmony_ci * snd_pci_quirk_lookup_id - look up a PCI SSID quirk list 1008c2ecf20Sopenharmony_ci * @vendor: PCI SSV id 1018c2ecf20Sopenharmony_ci * @device: PCI SSD id 1028c2ecf20Sopenharmony_ci * @list: quirk list, terminated by a null entry 1038c2ecf20Sopenharmony_ci * 1048c2ecf20Sopenharmony_ci * Look through the given quirk list and finds a matching entry 1058c2ecf20Sopenharmony_ci * with the same PCI SSID. When subdevice is 0, all subdevice 1068c2ecf20Sopenharmony_ci * values may match. 1078c2ecf20Sopenharmony_ci * 1088c2ecf20Sopenharmony_ci * Returns the matched entry pointer, or NULL if nothing matched. 1098c2ecf20Sopenharmony_ci */ 1108c2ecf20Sopenharmony_ciconst struct snd_pci_quirk * 1118c2ecf20Sopenharmony_cisnd_pci_quirk_lookup_id(u16 vendor, u16 device, 1128c2ecf20Sopenharmony_ci const struct snd_pci_quirk *list) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci const struct snd_pci_quirk *q; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci for (q = list; q->subvendor; q++) { 1178c2ecf20Sopenharmony_ci if (q->subvendor != vendor) 1188c2ecf20Sopenharmony_ci continue; 1198c2ecf20Sopenharmony_ci if (!q->subdevice || 1208c2ecf20Sopenharmony_ci (device & q->subdevice_mask) == q->subdevice) 1218c2ecf20Sopenharmony_ci return q; 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci return NULL; 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_pci_quirk_lookup_id); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci/** 1288c2ecf20Sopenharmony_ci * snd_pci_quirk_lookup - look up a PCI SSID quirk list 1298c2ecf20Sopenharmony_ci * @pci: pci_dev handle 1308c2ecf20Sopenharmony_ci * @list: quirk list, terminated by a null entry 1318c2ecf20Sopenharmony_ci * 1328c2ecf20Sopenharmony_ci * Look through the given quirk list and finds a matching entry 1338c2ecf20Sopenharmony_ci * with the same PCI SSID. When subdevice is 0, all subdevice 1348c2ecf20Sopenharmony_ci * values may match. 1358c2ecf20Sopenharmony_ci * 1368c2ecf20Sopenharmony_ci * Returns the matched entry pointer, or NULL if nothing matched. 1378c2ecf20Sopenharmony_ci */ 1388c2ecf20Sopenharmony_ciconst struct snd_pci_quirk * 1398c2ecf20Sopenharmony_cisnd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci if (!pci) 1428c2ecf20Sopenharmony_ci return NULL; 1438c2ecf20Sopenharmony_ci return snd_pci_quirk_lookup_id(pci->subsystem_vendor, 1448c2ecf20Sopenharmony_ci pci->subsystem_device, 1458c2ecf20Sopenharmony_ci list); 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_pci_quirk_lookup); 1488c2ecf20Sopenharmony_ci#endif 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci/* 1518c2ecf20Sopenharmony_ci * Deferred async signal helpers 1528c2ecf20Sopenharmony_ci * 1538c2ecf20Sopenharmony_ci * Below are a few helper functions to wrap the async signal handling 1548c2ecf20Sopenharmony_ci * in the deferred work. The main purpose is to avoid the messy deadlock 1558c2ecf20Sopenharmony_ci * around tasklist_lock and co at the kill_fasync() invocation. 1568c2ecf20Sopenharmony_ci * fasync_helper() and kill_fasync() are replaced with snd_fasync_helper() 1578c2ecf20Sopenharmony_ci * and snd_kill_fasync(), respectively. In addition, snd_fasync_free() has 1588c2ecf20Sopenharmony_ci * to be called at releasing the relevant file object. 1598c2ecf20Sopenharmony_ci */ 1608c2ecf20Sopenharmony_cistruct snd_fasync { 1618c2ecf20Sopenharmony_ci struct fasync_struct *fasync; 1628c2ecf20Sopenharmony_ci int signal; 1638c2ecf20Sopenharmony_ci int poll; 1648c2ecf20Sopenharmony_ci int on; 1658c2ecf20Sopenharmony_ci struct list_head list; 1668c2ecf20Sopenharmony_ci}; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(snd_fasync_lock); 1698c2ecf20Sopenharmony_cistatic LIST_HEAD(snd_fasync_list); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cistatic void snd_fasync_work_fn(struct work_struct *work) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci struct snd_fasync *fasync; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci spin_lock_irq(&snd_fasync_lock); 1768c2ecf20Sopenharmony_ci while (!list_empty(&snd_fasync_list)) { 1778c2ecf20Sopenharmony_ci fasync = list_first_entry(&snd_fasync_list, struct snd_fasync, list); 1788c2ecf20Sopenharmony_ci list_del_init(&fasync->list); 1798c2ecf20Sopenharmony_ci spin_unlock_irq(&snd_fasync_lock); 1808c2ecf20Sopenharmony_ci if (fasync->on) 1818c2ecf20Sopenharmony_ci kill_fasync(&fasync->fasync, fasync->signal, fasync->poll); 1828c2ecf20Sopenharmony_ci spin_lock_irq(&snd_fasync_lock); 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci spin_unlock_irq(&snd_fasync_lock); 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic DECLARE_WORK(snd_fasync_work, snd_fasync_work_fn); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ciint snd_fasync_helper(int fd, struct file *file, int on, 1908c2ecf20Sopenharmony_ci struct snd_fasync **fasyncp) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci struct snd_fasync *fasync = NULL; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci if (on) { 1958c2ecf20Sopenharmony_ci fasync = kzalloc(sizeof(*fasync), GFP_KERNEL); 1968c2ecf20Sopenharmony_ci if (!fasync) 1978c2ecf20Sopenharmony_ci return -ENOMEM; 1988c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&fasync->list); 1998c2ecf20Sopenharmony_ci } 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci spin_lock_irq(&snd_fasync_lock); 2028c2ecf20Sopenharmony_ci if (*fasyncp) { 2038c2ecf20Sopenharmony_ci kfree(fasync); 2048c2ecf20Sopenharmony_ci fasync = *fasyncp; 2058c2ecf20Sopenharmony_ci } else { 2068c2ecf20Sopenharmony_ci if (!fasync) { 2078c2ecf20Sopenharmony_ci spin_unlock_irq(&snd_fasync_lock); 2088c2ecf20Sopenharmony_ci return 0; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci *fasyncp = fasync; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci fasync->on = on; 2138c2ecf20Sopenharmony_ci spin_unlock_irq(&snd_fasync_lock); 2148c2ecf20Sopenharmony_ci return fasync_helper(fd, file, on, &fasync->fasync); 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_fasync_helper); 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_civoid snd_kill_fasync(struct snd_fasync *fasync, int signal, int poll) 2198c2ecf20Sopenharmony_ci{ 2208c2ecf20Sopenharmony_ci unsigned long flags; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci if (!fasync || !fasync->on) 2238c2ecf20Sopenharmony_ci return; 2248c2ecf20Sopenharmony_ci spin_lock_irqsave(&snd_fasync_lock, flags); 2258c2ecf20Sopenharmony_ci fasync->signal = signal; 2268c2ecf20Sopenharmony_ci fasync->poll = poll; 2278c2ecf20Sopenharmony_ci list_move(&fasync->list, &snd_fasync_list); 2288c2ecf20Sopenharmony_ci schedule_work(&snd_fasync_work); 2298c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&snd_fasync_lock, flags); 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_kill_fasync); 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_civoid snd_fasync_free(struct snd_fasync *fasync) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci if (!fasync) 2368c2ecf20Sopenharmony_ci return; 2378c2ecf20Sopenharmony_ci fasync->on = 0; 2388c2ecf20Sopenharmony_ci flush_work(&snd_fasync_work); 2398c2ecf20Sopenharmony_ci kfree(fasync); 2408c2ecf20Sopenharmony_ci} 2418c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_fasync_free); 242