xref: /openbmc/linux/sound/core/info.c (revision 2dac108b)
11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  *  Information interface for ALSA driver
4c1017a4cSJaroslav Kysela  *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
51da177e4SLinus Torvalds  */
61da177e4SLinus Torvalds 
71da177e4SLinus Torvalds #include <linux/init.h>
81da177e4SLinus Torvalds #include <linux/time.h>
927ac792cSAndrea Righi #include <linux/mm.h>
105a0e3ad6STejun Heo #include <linux/slab.h>
11543537bdSPaulo Marques #include <linux/string.h>
12da155d5bSPaul Gortmaker #include <linux/module.h>
131da177e4SLinus Torvalds #include <sound/core.h>
141da177e4SLinus Torvalds #include <sound/minors.h>
151da177e4SLinus Torvalds #include <sound/info.h>
1642662748SJaroslav Kysela #include <linux/utsname.h>
171da177e4SLinus Torvalds #include <linux/proc_fs.h>
181a60d4c5SIngo Molnar #include <linux/mutex.h>
191da177e4SLinus Torvalds 
snd_info_check_reserved_words(const char * str)201da177e4SLinus Torvalds int snd_info_check_reserved_words(const char *str)
211da177e4SLinus Torvalds {
2251d7847aSTakashi Iwai 	static const char * const reserved[] =
231da177e4SLinus Torvalds 	{
241da177e4SLinus Torvalds 		"version",
251da177e4SLinus Torvalds 		"meminfo",
261da177e4SLinus Torvalds 		"memdebug",
271da177e4SLinus Torvalds 		"detect",
281da177e4SLinus Torvalds 		"devices",
291da177e4SLinus Torvalds 		"oss",
301da177e4SLinus Torvalds 		"cards",
311da177e4SLinus Torvalds 		"timers",
321da177e4SLinus Torvalds 		"synth",
331da177e4SLinus Torvalds 		"pcm",
341da177e4SLinus Torvalds 		"seq",
351da177e4SLinus Torvalds 		NULL
361da177e4SLinus Torvalds 	};
3751d7847aSTakashi Iwai 	const char * const *xstr = reserved;
381da177e4SLinus Torvalds 
391da177e4SLinus Torvalds 	while (*xstr) {
401da177e4SLinus Torvalds 		if (!strcmp(*xstr, str))
411da177e4SLinus Torvalds 			return 0;
421da177e4SLinus Torvalds 		xstr++;
431da177e4SLinus Torvalds 	}
441da177e4SLinus Torvalds 	if (!strncmp(str, "card", 4))
451da177e4SLinus Torvalds 		return 0;
461da177e4SLinus Torvalds 	return 1;
471da177e4SLinus Torvalds }
481da177e4SLinus Torvalds 
491a60d4c5SIngo Molnar static DEFINE_MUTEX(info_mutex);
501da177e4SLinus Torvalds 
5124c1f931STakashi Iwai struct snd_info_private_data {
5224c1f931STakashi Iwai 	struct snd_info_buffer *rbuffer;
5324c1f931STakashi Iwai 	struct snd_info_buffer *wbuffer;
5424c1f931STakashi Iwai 	struct snd_info_entry *entry;
551da177e4SLinus Torvalds 	void *file_private_data;
5624c1f931STakashi Iwai };
571da177e4SLinus Torvalds 
581da177e4SLinus Torvalds static int snd_info_version_init(void);
59*2dac108bSTakashi Iwai static void snd_info_clear_entries(struct snd_info_entry *entry);
601da177e4SLinus Torvalds 
611da177e4SLinus Torvalds /*
621da177e4SLinus Torvalds 
631da177e4SLinus Torvalds  */
641da177e4SLinus Torvalds 
65644dbd64STakashi Iwai static struct snd_info_entry *snd_proc_root;
666581f4e7STakashi Iwai struct snd_info_entry *snd_seq_root;
67c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_seq_root);
68c0d3fb39STakashi Iwai 
691da177e4SLinus Torvalds #ifdef CONFIG_SND_OSSEMUL
706581f4e7STakashi Iwai struct snd_info_entry *snd_oss_root;
711da177e4SLinus Torvalds #endif
721da177e4SLinus Torvalds 
alloc_info_private(struct snd_info_entry * entry,struct snd_info_private_data ** ret)734adb7bcbSTakashi Iwai static int alloc_info_private(struct snd_info_entry *entry,
744adb7bcbSTakashi Iwai 			      struct snd_info_private_data **ret)
754adb7bcbSTakashi Iwai {
764adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
774adb7bcbSTakashi Iwai 
784adb7bcbSTakashi Iwai 	if (!entry || !entry->p)
794adb7bcbSTakashi Iwai 		return -ENODEV;
804adb7bcbSTakashi Iwai 	if (!try_module_get(entry->module))
814adb7bcbSTakashi Iwai 		return -EFAULT;
824adb7bcbSTakashi Iwai 	data = kzalloc(sizeof(*data), GFP_KERNEL);
834adb7bcbSTakashi Iwai 	if (!data) {
844adb7bcbSTakashi Iwai 		module_put(entry->module);
854adb7bcbSTakashi Iwai 		return -ENOMEM;
864adb7bcbSTakashi Iwai 	}
874adb7bcbSTakashi Iwai 	data->entry = entry;
884adb7bcbSTakashi Iwai 	*ret = data;
894adb7bcbSTakashi Iwai 	return 0;
904adb7bcbSTakashi Iwai }
914adb7bcbSTakashi Iwai 
valid_pos(loff_t pos,size_t count)924adb7bcbSTakashi Iwai static bool valid_pos(loff_t pos, size_t count)
934adb7bcbSTakashi Iwai {
944adb7bcbSTakashi Iwai 	if (pos < 0 || (long) pos != pos || (ssize_t) count < 0)
954adb7bcbSTakashi Iwai 		return false;
964adb7bcbSTakashi Iwai 	if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos)
974adb7bcbSTakashi Iwai 		return false;
984adb7bcbSTakashi Iwai 	return true;
994adb7bcbSTakashi Iwai }
1004adb7bcbSTakashi Iwai 
1014adb7bcbSTakashi Iwai /*
1024adb7bcbSTakashi Iwai  * file ops for binary proc files
1034adb7bcbSTakashi Iwai  */
snd_info_entry_llseek(struct file * file,loff_t offset,int orig)1041da177e4SLinus Torvalds static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig)
1051da177e4SLinus Torvalds {
10624c1f931STakashi Iwai 	struct snd_info_private_data *data;
1071da177e4SLinus Torvalds 	struct snd_info_entry *entry;
10873029e0fSTakashi Iwai 	loff_t ret = -EINVAL, size;
1091da177e4SLinus Torvalds 
1101da177e4SLinus Torvalds 	data = file->private_data;
1111da177e4SLinus Torvalds 	entry = data->entry;
1125b5cd553STakashi Iwai 	mutex_lock(&entry->access);
1134adb7bcbSTakashi Iwai 	if (entry->c.ops->llseek) {
1149be080edSAmadeusz Sławiński 		ret = entry->c.ops->llseek(entry,
1151da177e4SLinus Torvalds 					   data->file_private_data,
1161da177e4SLinus Torvalds 					   file, offset, orig);
1171da177e4SLinus Torvalds 		goto out;
1181da177e4SLinus Torvalds 	}
1194adb7bcbSTakashi Iwai 
12073029e0fSTakashi Iwai 	size = entry->size;
12173029e0fSTakashi Iwai 	switch (orig) {
12273029e0fSTakashi Iwai 	case SEEK_SET:
1231da177e4SLinus Torvalds 		break;
12473029e0fSTakashi Iwai 	case SEEK_CUR:
12573029e0fSTakashi Iwai 		offset += file->f_pos;
12673029e0fSTakashi Iwai 		break;
12773029e0fSTakashi Iwai 	case SEEK_END:
12873029e0fSTakashi Iwai 		if (!size)
12973029e0fSTakashi Iwai 			goto out;
13073029e0fSTakashi Iwai 		offset += size;
13173029e0fSTakashi Iwai 		break;
13273029e0fSTakashi Iwai 	default:
13373029e0fSTakashi Iwai 		goto out;
1341da177e4SLinus Torvalds 	}
13573029e0fSTakashi Iwai 	if (offset < 0)
13673029e0fSTakashi Iwai 		goto out;
13773029e0fSTakashi Iwai 	if (size && offset > size)
13873029e0fSTakashi Iwai 		offset = size;
13973029e0fSTakashi Iwai 	file->f_pos = offset;
14073029e0fSTakashi Iwai 	ret = offset;
1411da177e4SLinus Torvalds  out:
1425b5cd553STakashi Iwai 	mutex_unlock(&entry->access);
1431da177e4SLinus Torvalds 	return ret;
1441da177e4SLinus Torvalds }
1451da177e4SLinus Torvalds 
snd_info_entry_read(struct file * file,char __user * buffer,size_t count,loff_t * offset)1461da177e4SLinus Torvalds static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
1471da177e4SLinus Torvalds 				   size_t count, loff_t * offset)
1481da177e4SLinus Torvalds {
1494adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1504adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
1514adb7bcbSTakashi Iwai 	size_t size;
1521da177e4SLinus Torvalds 	loff_t pos;
1531da177e4SLinus Torvalds 
1541da177e4SLinus Torvalds 	pos = *offset;
1554adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
1561da177e4SLinus Torvalds 		return -EIO;
157d97e1b78STakashi Iwai 	if (pos >= entry->size)
158d97e1b78STakashi Iwai 		return 0;
159d97e1b78STakashi Iwai 	size = entry->size - pos;
160d97e1b78STakashi Iwai 	size = min(count, size);
1614adb7bcbSTakashi Iwai 	size = entry->c.ops->read(entry, data->file_private_data,
162d97e1b78STakashi Iwai 				  file, buffer, size, pos);
1631da177e4SLinus Torvalds 	if ((ssize_t) size > 0)
1641da177e4SLinus Torvalds 		*offset = pos + size;
1651da177e4SLinus Torvalds 	return size;
1661da177e4SLinus Torvalds }
1671da177e4SLinus Torvalds 
snd_info_entry_write(struct file * file,const char __user * buffer,size_t count,loff_t * offset)1681da177e4SLinus Torvalds static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer,
1691da177e4SLinus Torvalds 				    size_t count, loff_t * offset)
1701da177e4SLinus Torvalds {
1714adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1724adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
1737e4eeec8STakashi Iwai 	ssize_t size = 0;
1741da177e4SLinus Torvalds 	loff_t pos;
1751da177e4SLinus Torvalds 
1761da177e4SLinus Torvalds 	pos = *offset;
1774adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
1781da177e4SLinus Torvalds 		return -EIO;
1794adb7bcbSTakashi Iwai 	if (count > 0) {
180d97e1b78STakashi Iwai 		size_t maxsize = entry->size - pos;
181d97e1b78STakashi Iwai 		count = min(count, maxsize);
1824adb7bcbSTakashi Iwai 		size = entry->c.ops->write(entry, data->file_private_data,
1831da177e4SLinus Torvalds 					   file, buffer, count, pos);
184d97e1b78STakashi Iwai 	}
1854adb7bcbSTakashi Iwai 	if (size > 0)
1861da177e4SLinus Torvalds 		*offset = pos + size;
1871da177e4SLinus Torvalds 	return size;
1881da177e4SLinus Torvalds }
1891da177e4SLinus Torvalds 
snd_info_entry_poll(struct file * file,poll_table * wait)190680ef72aSAl Viro static __poll_t snd_info_entry_poll(struct file *file, poll_table *wait)
1911da177e4SLinus Torvalds {
1924adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1934adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
194680ef72aSAl Viro 	__poll_t mask = 0;
1951da177e4SLinus Torvalds 
1961da177e4SLinus Torvalds 	if (entry->c.ops->poll)
1971da177e4SLinus Torvalds 		return entry->c.ops->poll(entry,
1981da177e4SLinus Torvalds 					  data->file_private_data,
1991da177e4SLinus Torvalds 					  file, wait);
2001da177e4SLinus Torvalds 	if (entry->c.ops->read)
201a9a08845SLinus Torvalds 		mask |= EPOLLIN | EPOLLRDNORM;
2021da177e4SLinus Torvalds 	if (entry->c.ops->write)
203a9a08845SLinus Torvalds 		mask |= EPOLLOUT | EPOLLWRNORM;
2041da177e4SLinus Torvalds 	return mask;
2051da177e4SLinus Torvalds }
2061da177e4SLinus Torvalds 
snd_info_entry_ioctl(struct file * file,unsigned int cmd,unsigned long arg)207d99e9889SIngo Molnar static long snd_info_entry_ioctl(struct file *file, unsigned int cmd,
208d99e9889SIngo Molnar 				unsigned long arg)
2091da177e4SLinus Torvalds {
2104adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2114adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2121da177e4SLinus Torvalds 
2134adb7bcbSTakashi Iwai 	if (!entry->c.ops->ioctl)
2141da177e4SLinus Torvalds 		return -ENOTTY;
2154adb7bcbSTakashi Iwai 	return entry->c.ops->ioctl(entry, data->file_private_data,
2164adb7bcbSTakashi Iwai 				   file, cmd, arg);
2171da177e4SLinus Torvalds }
2181da177e4SLinus Torvalds 
snd_info_entry_mmap(struct file * file,struct vm_area_struct * vma)2191da177e4SLinus Torvalds static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma)
2201da177e4SLinus Torvalds {
221496ad9aaSAl Viro 	struct inode *inode = file_inode(file);
22224c1f931STakashi Iwai 	struct snd_info_private_data *data;
2231da177e4SLinus Torvalds 	struct snd_info_entry *entry;
2241da177e4SLinus Torvalds 
2251da177e4SLinus Torvalds 	data = file->private_data;
2261da177e4SLinus Torvalds 	if (data == NULL)
2271da177e4SLinus Torvalds 		return 0;
2281da177e4SLinus Torvalds 	entry = data->entry;
2294adb7bcbSTakashi Iwai 	if (!entry->c.ops->mmap)
2301da177e4SLinus Torvalds 		return -ENXIO;
2314adb7bcbSTakashi Iwai 	return entry->c.ops->mmap(entry, data->file_private_data,
2324adb7bcbSTakashi Iwai 				  inode, file, vma);
2334adb7bcbSTakashi Iwai }
2344adb7bcbSTakashi Iwai 
snd_info_entry_open(struct inode * inode,struct file * file)2354adb7bcbSTakashi Iwai static int snd_info_entry_open(struct inode *inode, struct file *file)
2364adb7bcbSTakashi Iwai {
237359745d7SMuchun Song 	struct snd_info_entry *entry = pde_data(inode);
2384adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
2394adb7bcbSTakashi Iwai 	int mode, err;
2404adb7bcbSTakashi Iwai 
2414adb7bcbSTakashi Iwai 	mutex_lock(&info_mutex);
2424adb7bcbSTakashi Iwai 	err = alloc_info_private(entry, &data);
2434adb7bcbSTakashi Iwai 	if (err < 0)
2444adb7bcbSTakashi Iwai 		goto unlock;
2454adb7bcbSTakashi Iwai 
2464adb7bcbSTakashi Iwai 	mode = file->f_flags & O_ACCMODE;
2474adb7bcbSTakashi Iwai 	if (((mode == O_RDONLY || mode == O_RDWR) && !entry->c.ops->read) ||
2484adb7bcbSTakashi Iwai 	    ((mode == O_WRONLY || mode == O_RDWR) && !entry->c.ops->write)) {
2494adb7bcbSTakashi Iwai 		err = -ENODEV;
2504adb7bcbSTakashi Iwai 		goto error;
2514adb7bcbSTakashi Iwai 	}
2524adb7bcbSTakashi Iwai 
2534adb7bcbSTakashi Iwai 	if (entry->c.ops->open) {
2544adb7bcbSTakashi Iwai 		err = entry->c.ops->open(entry, mode, &data->file_private_data);
2554adb7bcbSTakashi Iwai 		if (err < 0)
2564adb7bcbSTakashi Iwai 			goto error;
2574adb7bcbSTakashi Iwai 	}
2584adb7bcbSTakashi Iwai 
2594adb7bcbSTakashi Iwai 	file->private_data = data;
2604adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
2614adb7bcbSTakashi Iwai 	return 0;
2624adb7bcbSTakashi Iwai 
2634adb7bcbSTakashi Iwai  error:
2644adb7bcbSTakashi Iwai 	kfree(data);
2654adb7bcbSTakashi Iwai 	module_put(entry->module);
2664adb7bcbSTakashi Iwai  unlock:
2674adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
2684adb7bcbSTakashi Iwai 	return err;
2694adb7bcbSTakashi Iwai }
2704adb7bcbSTakashi Iwai 
snd_info_entry_release(struct inode * inode,struct file * file)2714adb7bcbSTakashi Iwai static int snd_info_entry_release(struct inode *inode, struct file *file)
2724adb7bcbSTakashi Iwai {
2734adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2744adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2754adb7bcbSTakashi Iwai 
2764adb7bcbSTakashi Iwai 	if (entry->c.ops->release)
2774adb7bcbSTakashi Iwai 		entry->c.ops->release(entry, file->f_flags & O_ACCMODE,
2784adb7bcbSTakashi Iwai 				      data->file_private_data);
2794adb7bcbSTakashi Iwai 	module_put(entry->module);
2804adb7bcbSTakashi Iwai 	kfree(data);
2814adb7bcbSTakashi Iwai 	return 0;
2821da177e4SLinus Torvalds }
2831da177e4SLinus Torvalds 
28497a32539SAlexey Dobriyan static const struct proc_ops snd_info_entry_operations =
2851da177e4SLinus Torvalds {
28697a32539SAlexey Dobriyan 	.proc_lseek	= snd_info_entry_llseek,
28797a32539SAlexey Dobriyan 	.proc_read	= snd_info_entry_read,
28897a32539SAlexey Dobriyan 	.proc_write	= snd_info_entry_write,
28997a32539SAlexey Dobriyan 	.proc_poll	= snd_info_entry_poll,
29097a32539SAlexey Dobriyan 	.proc_ioctl	= snd_info_entry_ioctl,
29197a32539SAlexey Dobriyan 	.proc_mmap	= snd_info_entry_mmap,
29297a32539SAlexey Dobriyan 	.proc_open	= snd_info_entry_open,
29397a32539SAlexey Dobriyan 	.proc_release	= snd_info_entry_release,
2941da177e4SLinus Torvalds };
2951da177e4SLinus Torvalds 
2964adb7bcbSTakashi Iwai /*
2974adb7bcbSTakashi Iwai  * file ops for text proc files
2984adb7bcbSTakashi Iwai  */
snd_info_text_entry_write(struct file * file,const char __user * buffer,size_t count,loff_t * offset)2994adb7bcbSTakashi Iwai static ssize_t snd_info_text_entry_write(struct file *file,
3004adb7bcbSTakashi Iwai 					 const char __user *buffer,
3014adb7bcbSTakashi Iwai 					 size_t count, loff_t *offset)
3024adb7bcbSTakashi Iwai {
3034adb7bcbSTakashi Iwai 	struct seq_file *m = file->private_data;
3044adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = m->private;
3054adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
3064adb7bcbSTakashi Iwai 	struct snd_info_buffer *buf;
3074adb7bcbSTakashi Iwai 	loff_t pos;
3084adb7bcbSTakashi Iwai 	size_t next;
3094adb7bcbSTakashi Iwai 	int err = 0;
3104adb7bcbSTakashi Iwai 
3116809cd68STakashi Iwai 	if (!entry->c.text.write)
3126809cd68STakashi Iwai 		return -EIO;
3134adb7bcbSTakashi Iwai 	pos = *offset;
3144adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
3154adb7bcbSTakashi Iwai 		return -EIO;
3164adb7bcbSTakashi Iwai 	next = pos + count;
317027a9fe6STakashi Iwai 	/* don't handle too large text inputs */
318027a9fe6STakashi Iwai 	if (next > 16 * 1024)
319027a9fe6STakashi Iwai 		return -EIO;
3204adb7bcbSTakashi Iwai 	mutex_lock(&entry->access);
3214adb7bcbSTakashi Iwai 	buf = data->wbuffer;
3224adb7bcbSTakashi Iwai 	if (!buf) {
3234adb7bcbSTakashi Iwai 		data->wbuffer = buf = kzalloc(sizeof(*buf), GFP_KERNEL);
3244adb7bcbSTakashi Iwai 		if (!buf) {
3254adb7bcbSTakashi Iwai 			err = -ENOMEM;
3264adb7bcbSTakashi Iwai 			goto error;
3274adb7bcbSTakashi Iwai 		}
3284adb7bcbSTakashi Iwai 	}
3294adb7bcbSTakashi Iwai 	if (next > buf->len) {
330ffb73b08STakashi Iwai 		char *nbuf = kvzalloc(PAGE_ALIGN(next), GFP_KERNEL);
3314adb7bcbSTakashi Iwai 		if (!nbuf) {
3324adb7bcbSTakashi Iwai 			err = -ENOMEM;
3334adb7bcbSTakashi Iwai 			goto error;
3344adb7bcbSTakashi Iwai 		}
335ffb73b08STakashi Iwai 		kvfree(buf->buffer);
3364adb7bcbSTakashi Iwai 		buf->buffer = nbuf;
3374adb7bcbSTakashi Iwai 		buf->len = PAGE_ALIGN(next);
3384adb7bcbSTakashi Iwai 	}
3394adb7bcbSTakashi Iwai 	if (copy_from_user(buf->buffer + pos, buffer, count)) {
3404adb7bcbSTakashi Iwai 		err = -EFAULT;
3414adb7bcbSTakashi Iwai 		goto error;
3424adb7bcbSTakashi Iwai 	}
3434adb7bcbSTakashi Iwai 	buf->size = next;
3444adb7bcbSTakashi Iwai  error:
3454adb7bcbSTakashi Iwai 	mutex_unlock(&entry->access);
3464adb7bcbSTakashi Iwai 	if (err < 0)
3474adb7bcbSTakashi Iwai 		return err;
3484adb7bcbSTakashi Iwai 	*offset = next;
3494adb7bcbSTakashi Iwai 	return count;
3504adb7bcbSTakashi Iwai }
3514adb7bcbSTakashi Iwai 
snd_info_seq_show(struct seq_file * seq,void * p)3524adb7bcbSTakashi Iwai static int snd_info_seq_show(struct seq_file *seq, void *p)
3534adb7bcbSTakashi Iwai {
3544adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = seq->private;
3554adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
3564adb7bcbSTakashi Iwai 
3576809cd68STakashi Iwai 	if (!entry->c.text.read) {
3586809cd68STakashi Iwai 		return -EIO;
3596809cd68STakashi Iwai 	} else {
3604adb7bcbSTakashi Iwai 		data->rbuffer->buffer = (char *)seq; /* XXX hack! */
3614adb7bcbSTakashi Iwai 		entry->c.text.read(entry, data->rbuffer);
3624adb7bcbSTakashi Iwai 	}
3634adb7bcbSTakashi Iwai 	return 0;
3644adb7bcbSTakashi Iwai }
3654adb7bcbSTakashi Iwai 
snd_info_text_entry_open(struct inode * inode,struct file * file)3664adb7bcbSTakashi Iwai static int snd_info_text_entry_open(struct inode *inode, struct file *file)
3674adb7bcbSTakashi Iwai {
368359745d7SMuchun Song 	struct snd_info_entry *entry = pde_data(inode);
3694adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
3704adb7bcbSTakashi Iwai 	int err;
3714adb7bcbSTakashi Iwai 
3724adb7bcbSTakashi Iwai 	mutex_lock(&info_mutex);
3734adb7bcbSTakashi Iwai 	err = alloc_info_private(entry, &data);
3744adb7bcbSTakashi Iwai 	if (err < 0)
3754adb7bcbSTakashi Iwai 		goto unlock;
3764adb7bcbSTakashi Iwai 
3774adb7bcbSTakashi Iwai 	data->rbuffer = kzalloc(sizeof(*data->rbuffer), GFP_KERNEL);
3784adb7bcbSTakashi Iwai 	if (!data->rbuffer) {
3794adb7bcbSTakashi Iwai 		err = -ENOMEM;
3804adb7bcbSTakashi Iwai 		goto error;
3814adb7bcbSTakashi Iwai 	}
3824adb7bcbSTakashi Iwai 	if (entry->size)
3834adb7bcbSTakashi Iwai 		err = single_open_size(file, snd_info_seq_show, data,
3844adb7bcbSTakashi Iwai 				       entry->size);
3854adb7bcbSTakashi Iwai 	else
3864adb7bcbSTakashi Iwai 		err = single_open(file, snd_info_seq_show, data);
3874adb7bcbSTakashi Iwai 	if (err < 0)
3884adb7bcbSTakashi Iwai 		goto error;
3894adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
3904adb7bcbSTakashi Iwai 	return 0;
3914adb7bcbSTakashi Iwai 
3924adb7bcbSTakashi Iwai  error:
3934adb7bcbSTakashi Iwai 	kfree(data->rbuffer);
3944adb7bcbSTakashi Iwai 	kfree(data);
3954adb7bcbSTakashi Iwai 	module_put(entry->module);
3964adb7bcbSTakashi Iwai  unlock:
3974adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
3984adb7bcbSTakashi Iwai 	return err;
3994adb7bcbSTakashi Iwai }
4004adb7bcbSTakashi Iwai 
snd_info_text_entry_release(struct inode * inode,struct file * file)4014adb7bcbSTakashi Iwai static int snd_info_text_entry_release(struct inode *inode, struct file *file)
4024adb7bcbSTakashi Iwai {
4034adb7bcbSTakashi Iwai 	struct seq_file *m = file->private_data;
4044adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = m->private;
4054adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
4064adb7bcbSTakashi Iwai 
4074adb7bcbSTakashi Iwai 	if (data->wbuffer && entry->c.text.write)
4084adb7bcbSTakashi Iwai 		entry->c.text.write(entry, data->wbuffer);
4094adb7bcbSTakashi Iwai 
4104adb7bcbSTakashi Iwai 	single_release(inode, file);
4114adb7bcbSTakashi Iwai 	kfree(data->rbuffer);
4124adb7bcbSTakashi Iwai 	if (data->wbuffer) {
413ffb73b08STakashi Iwai 		kvfree(data->wbuffer->buffer);
4144adb7bcbSTakashi Iwai 		kfree(data->wbuffer);
4154adb7bcbSTakashi Iwai 	}
4164adb7bcbSTakashi Iwai 
4174adb7bcbSTakashi Iwai 	module_put(entry->module);
4184adb7bcbSTakashi Iwai 	kfree(data);
4194adb7bcbSTakashi Iwai 	return 0;
4204adb7bcbSTakashi Iwai }
4214adb7bcbSTakashi Iwai 
42297a32539SAlexey Dobriyan static const struct proc_ops snd_info_text_entry_ops =
4234adb7bcbSTakashi Iwai {
42497a32539SAlexey Dobriyan 	.proc_open	= snd_info_text_entry_open,
42597a32539SAlexey Dobriyan 	.proc_release	= snd_info_text_entry_release,
42697a32539SAlexey Dobriyan 	.proc_write	= snd_info_text_entry_write,
42797a32539SAlexey Dobriyan 	.proc_lseek	= seq_lseek,
42897a32539SAlexey Dobriyan 	.proc_read	= seq_read,
4294adb7bcbSTakashi Iwai };
4304adb7bcbSTakashi Iwai 
create_subdir(struct module * mod,const char * name)431886364f6STakashi Iwai static struct snd_info_entry *create_subdir(struct module *mod,
432886364f6STakashi Iwai 					    const char *name)
433886364f6STakashi Iwai {
434886364f6STakashi Iwai 	struct snd_info_entry *entry;
435886364f6STakashi Iwai 
436886364f6STakashi Iwai 	entry = snd_info_create_module_entry(mod, name, NULL);
437886364f6STakashi Iwai 	if (!entry)
438886364f6STakashi Iwai 		return NULL;
4396a73cf46SJoe Perches 	entry->mode = S_IFDIR | 0555;
440886364f6STakashi Iwai 	if (snd_info_register(entry) < 0) {
441886364f6STakashi Iwai 		snd_info_free_entry(entry);
442886364f6STakashi Iwai 		return NULL;
443886364f6STakashi Iwai 	}
444886364f6STakashi Iwai 	return entry;
445886364f6STakashi Iwai }
446886364f6STakashi Iwai 
4478e7ccb7bSTakashi Iwai static struct snd_info_entry *
448a858ee66STakashi Iwai snd_info_create_entry(const char *name, struct snd_info_entry *parent,
449a858ee66STakashi Iwai 		      struct module *module);
450644dbd64STakashi Iwai 
snd_info_init(void)4511da177e4SLinus Torvalds int __init snd_info_init(void)
4521da177e4SLinus Torvalds {
453a858ee66STakashi Iwai 	snd_proc_root = snd_info_create_entry("asound", NULL, THIS_MODULE);
454644dbd64STakashi Iwai 	if (!snd_proc_root)
4551da177e4SLinus Torvalds 		return -ENOMEM;
4566a73cf46SJoe Perches 	snd_proc_root->mode = S_IFDIR | 0555;
457644dbd64STakashi Iwai 	snd_proc_root->p = proc_mkdir("asound", NULL);
458644dbd64STakashi Iwai 	if (!snd_proc_root->p)
459644dbd64STakashi Iwai 		goto error;
4601da177e4SLinus Torvalds #ifdef CONFIG_SND_OSSEMUL
461886364f6STakashi Iwai 	snd_oss_root = create_subdir(THIS_MODULE, "oss");
462886364f6STakashi Iwai 	if (!snd_oss_root)
463886364f6STakashi Iwai 		goto error;
4641da177e4SLinus Torvalds #endif
4658eeaa2f9STakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
466886364f6STakashi Iwai 	snd_seq_root = create_subdir(THIS_MODULE, "seq");
467886364f6STakashi Iwai 	if (!snd_seq_root)
468886364f6STakashi Iwai 		goto error;
4691da177e4SLinus Torvalds #endif
470b591b6e9STakashi Iwai 	if (snd_info_version_init() < 0 ||
471b591b6e9STakashi Iwai 	    snd_minor_info_init() < 0 ||
472b591b6e9STakashi Iwai 	    snd_minor_info_oss_init() < 0 ||
473a0dca822STakashi Iwai 	    snd_card_info_init() < 0 ||
474a0dca822STakashi Iwai 	    snd_info_minor_register() < 0)
475b591b6e9STakashi Iwai 		goto error;
4761da177e4SLinus Torvalds 	return 0;
477886364f6STakashi Iwai 
478886364f6STakashi Iwai  error:
479644dbd64STakashi Iwai 	snd_info_free_entry(snd_proc_root);
480886364f6STakashi Iwai 	return -ENOMEM;
4811da177e4SLinus Torvalds }
4821da177e4SLinus Torvalds 
snd_info_done(void)4831da177e4SLinus Torvalds int __exit snd_info_done(void)
4841da177e4SLinus Torvalds {
485644dbd64STakashi Iwai 	snd_info_free_entry(snd_proc_root);
4861da177e4SLinus Torvalds 	return 0;
4871da177e4SLinus Torvalds }
4881da177e4SLinus Torvalds 
snd_card_id_read(struct snd_info_entry * entry,struct snd_info_buffer * buffer)48929b2625fSTakashi Iwai static void snd_card_id_read(struct snd_info_entry *entry,
49029b2625fSTakashi Iwai 			     struct snd_info_buffer *buffer)
49129b2625fSTakashi Iwai {
49229b2625fSTakashi Iwai 	struct snd_card *card = entry->private_data;
49329b2625fSTakashi Iwai 
49429b2625fSTakashi Iwai 	snd_iprintf(buffer, "%s\n", card->id);
49529b2625fSTakashi Iwai }
49629b2625fSTakashi Iwai 
4971da177e4SLinus Torvalds /*
4981da177e4SLinus Torvalds  * create a card proc file
4991da177e4SLinus Torvalds  * called from init.c
5001da177e4SLinus Torvalds  */
snd_info_card_create(struct snd_card * card)50124c1f931STakashi Iwai int snd_info_card_create(struct snd_card *card)
5021da177e4SLinus Torvalds {
5031da177e4SLinus Torvalds 	char str[8];
50424c1f931STakashi Iwai 	struct snd_info_entry *entry;
5051da177e4SLinus Torvalds 
5067eaa943cSTakashi Iwai 	if (snd_BUG_ON(!card))
5077eaa943cSTakashi Iwai 		return -ENXIO;
5081da177e4SLinus Torvalds 
5091da177e4SLinus Torvalds 	sprintf(str, "card%i", card->number);
510886364f6STakashi Iwai 	entry = create_subdir(card->module, str);
511886364f6STakashi Iwai 	if (!entry)
5121da177e4SLinus Torvalds 		return -ENOMEM;
5131da177e4SLinus Torvalds 	card->proc_root = entry;
51429b2625fSTakashi Iwai 
51529b2625fSTakashi Iwai 	return snd_card_ro_proc_new(card, "id", card, snd_card_id_read);
5161da177e4SLinus Torvalds }
5171da177e4SLinus Torvalds 
5181da177e4SLinus Torvalds /*
5191da177e4SLinus Torvalds  * register the card proc file
5201da177e4SLinus Torvalds  * called from init.c
5212471b6c8STakashi Iwai  * can be called multiple times for reinitialization
5221da177e4SLinus Torvalds  */
snd_info_card_register(struct snd_card * card)52324c1f931STakashi Iwai int snd_info_card_register(struct snd_card *card)
5241da177e4SLinus Torvalds {
5251da177e4SLinus Torvalds 	struct proc_dir_entry *p;
5262471b6c8STakashi Iwai 	int err;
5271da177e4SLinus Torvalds 
5287eaa943cSTakashi Iwai 	if (snd_BUG_ON(!card))
5297eaa943cSTakashi Iwai 		return -ENXIO;
5301da177e4SLinus Torvalds 
531348c5ad5STakashi Iwai 	err = snd_info_register(card->proc_root);
5322471b6c8STakashi Iwai 	if (err < 0)
5332471b6c8STakashi Iwai 		return err;
5342471b6c8STakashi Iwai 
5351da177e4SLinus Torvalds 	if (!strcmp(card->id, card->proc_root->name))
5361da177e4SLinus Torvalds 		return 0;
5371da177e4SLinus Torvalds 
5382471b6c8STakashi Iwai 	if (card->proc_root_link)
5392471b6c8STakashi Iwai 		return 0;
540644dbd64STakashi Iwai 	p = proc_symlink(card->id, snd_proc_root->p, card->proc_root->name);
5412471b6c8STakashi Iwai 	if (!p)
5421da177e4SLinus Torvalds 		return -ENOMEM;
5431da177e4SLinus Torvalds 	card->proc_root_link = p;
5441da177e4SLinus Torvalds 	return 0;
5451da177e4SLinus Torvalds }
5461da177e4SLinus Torvalds 
5471da177e4SLinus Torvalds /*
548c2eb9c4eSJaroslav Kysela  * called on card->id change
549c2eb9c4eSJaroslav Kysela  */
snd_info_card_id_change(struct snd_card * card)550c2eb9c4eSJaroslav Kysela void snd_info_card_id_change(struct snd_card *card)
551c2eb9c4eSJaroslav Kysela {
552c2eb9c4eSJaroslav Kysela 	mutex_lock(&info_mutex);
553c2eb9c4eSJaroslav Kysela 	if (card->proc_root_link) {
554a8ca16eaSDavid Howells 		proc_remove(card->proc_root_link);
555c2eb9c4eSJaroslav Kysela 		card->proc_root_link = NULL;
556c2eb9c4eSJaroslav Kysela 	}
557c2eb9c4eSJaroslav Kysela 	if (strcmp(card->id, card->proc_root->name))
558c2eb9c4eSJaroslav Kysela 		card->proc_root_link = proc_symlink(card->id,
559644dbd64STakashi Iwai 						    snd_proc_root->p,
560c2eb9c4eSJaroslav Kysela 						    card->proc_root->name);
561c2eb9c4eSJaroslav Kysela 	mutex_unlock(&info_mutex);
562c2eb9c4eSJaroslav Kysela }
563c2eb9c4eSJaroslav Kysela 
564c2eb9c4eSJaroslav Kysela /*
5651da177e4SLinus Torvalds  * de-register the card proc file
5661da177e4SLinus Torvalds  * called from init.c
5671da177e4SLinus Torvalds  */
snd_info_card_disconnect(struct snd_card * card)568746d4a02STakashi Iwai void snd_info_card_disconnect(struct snd_card *card)
5691da177e4SLinus Torvalds {
5707eaa943cSTakashi Iwai 	if (!card)
5717eaa943cSTakashi Iwai 		return;
572*2dac108bSTakashi Iwai 
573a8ca16eaSDavid Howells 	proc_remove(card->proc_root_link);
574746d4a02STakashi Iwai 	if (card->proc_root)
575*2dac108bSTakashi Iwai 		proc_remove(card->proc_root->p);
576*2dac108bSTakashi Iwai 
577*2dac108bSTakashi Iwai 	mutex_lock(&info_mutex);
578*2dac108bSTakashi Iwai 	if (card->proc_root)
579*2dac108bSTakashi Iwai 		snd_info_clear_entries(card->proc_root);
580*2dac108bSTakashi Iwai 	card->proc_root_link = NULL;
581*2dac108bSTakashi Iwai 	card->proc_root = NULL;
582746d4a02STakashi Iwai 	mutex_unlock(&info_mutex);
5831da177e4SLinus Torvalds }
584746d4a02STakashi Iwai 
585746d4a02STakashi Iwai /*
586746d4a02STakashi Iwai  * release the card proc file resources
587746d4a02STakashi Iwai  * called from init.c
588746d4a02STakashi Iwai  */
snd_info_card_free(struct snd_card * card)589746d4a02STakashi Iwai int snd_info_card_free(struct snd_card *card)
590746d4a02STakashi Iwai {
5917eaa943cSTakashi Iwai 	if (!card)
5927eaa943cSTakashi Iwai 		return 0;
593746d4a02STakashi Iwai 	snd_info_free_entry(card->proc_root);
594746d4a02STakashi Iwai 	card->proc_root = NULL;
5951da177e4SLinus Torvalds 	return 0;
5961da177e4SLinus Torvalds }
5971da177e4SLinus Torvalds 
5981da177e4SLinus Torvalds 
5991da177e4SLinus Torvalds /**
6001da177e4SLinus Torvalds  * snd_info_get_line - read one line from the procfs buffer
6011da177e4SLinus Torvalds  * @buffer: the procfs buffer
6021da177e4SLinus Torvalds  * @line: the buffer to store
603ddc64b27SClemens Ladisch  * @len: the max. buffer size
6041da177e4SLinus Torvalds  *
6051da177e4SLinus Torvalds  * Reads one line from the buffer and stores the string.
6061da177e4SLinus Torvalds  *
607eb7c06e8SYacine Belkadi  * Return: Zero if successful, or 1 if error or EOF.
6081da177e4SLinus Torvalds  */
snd_info_get_line(struct snd_info_buffer * buffer,char * line,int len)60924c1f931STakashi Iwai int snd_info_get_line(struct snd_info_buffer *buffer, char *line, int len)
6101da177e4SLinus Torvalds {
6110e023687SColin Ian King 	int c;
6121da177e4SLinus Torvalds 
61360379ba0STakashi Iwai 	if (snd_BUG_ON(!buffer))
61460379ba0STakashi Iwai 		return 1;
61560379ba0STakashi Iwai 	if (!buffer->buffer)
6160bc0ec90STakashi Iwai 		return 1;
6171da177e4SLinus Torvalds 	if (len <= 0 || buffer->stop || buffer->error)
6181da177e4SLinus Torvalds 		return 1;
6190bc0ec90STakashi Iwai 	while (!buffer->stop) {
6207e4eeec8STakashi Iwai 		c = buffer->buffer[buffer->curr++];
6217e4eeec8STakashi Iwai 		if (buffer->curr >= buffer->size)
6221da177e4SLinus Torvalds 			buffer->stop = 1;
6230bc0ec90STakashi Iwai 		if (c == '\n')
6241da177e4SLinus Torvalds 			break;
625ddc64b27SClemens Ladisch 		if (len > 1) {
6260bc0ec90STakashi Iwai 			len--;
6271da177e4SLinus Torvalds 			*line++ = c;
6281da177e4SLinus Torvalds 		}
6291da177e4SLinus Torvalds 	}
6301da177e4SLinus Torvalds 	*line = '\0';
6311da177e4SLinus Torvalds 	return 0;
6321da177e4SLinus Torvalds }
633c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_get_line);
634c0d3fb39STakashi Iwai 
6351da177e4SLinus Torvalds /**
636856def8aSHenrik Kretzschmar  * snd_info_get_str - parse a string token
6371da177e4SLinus Torvalds  * @dest: the buffer to store the string token
6381da177e4SLinus Torvalds  * @src: the original string
6391da177e4SLinus Torvalds  * @len: the max. length of token - 1
6401da177e4SLinus Torvalds  *
6411da177e4SLinus Torvalds  * Parses the original string and copy a token to the given
6421da177e4SLinus Torvalds  * string buffer.
6431da177e4SLinus Torvalds  *
644eb7c06e8SYacine Belkadi  * Return: The updated pointer of the original string so that
6451da177e4SLinus Torvalds  * it can be used for the next call.
6461da177e4SLinus Torvalds  */
snd_info_get_str(char * dest,const char * src,int len)6474f7454a9STakashi Iwai const char *snd_info_get_str(char *dest, const char *src, int len)
6481da177e4SLinus Torvalds {
6491da177e4SLinus Torvalds 	int c;
6501da177e4SLinus Torvalds 
6511da177e4SLinus Torvalds 	while (*src == ' ' || *src == '\t')
6521da177e4SLinus Torvalds 		src++;
6531da177e4SLinus Torvalds 	if (*src == '"' || *src == '\'') {
6541da177e4SLinus Torvalds 		c = *src++;
6551da177e4SLinus Torvalds 		while (--len > 0 && *src && *src != c) {
6561da177e4SLinus Torvalds 			*dest++ = *src++;
6571da177e4SLinus Torvalds 		}
6581da177e4SLinus Torvalds 		if (*src == c)
6591da177e4SLinus Torvalds 			src++;
6601da177e4SLinus Torvalds 	} else {
6611da177e4SLinus Torvalds 		while (--len > 0 && *src && *src != ' ' && *src != '\t') {
6621da177e4SLinus Torvalds 			*dest++ = *src++;
6631da177e4SLinus Torvalds 		}
6641da177e4SLinus Torvalds 	}
6651da177e4SLinus Torvalds 	*dest = 0;
6661da177e4SLinus Torvalds 	while (*src == ' ' || *src == '\t')
6671da177e4SLinus Torvalds 		src++;
6681da177e4SLinus Torvalds 	return src;
6691da177e4SLinus Torvalds }
670c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_get_str);
671c0d3fb39STakashi Iwai 
672c309c467STakashi Iwai /*
6731da177e4SLinus Torvalds  * snd_info_create_entry - create an info entry
6741da177e4SLinus Torvalds  * @name: the proc file name
6758e7ccb7bSTakashi Iwai  * @parent: the parent directory
6761da177e4SLinus Torvalds  *
6771da177e4SLinus Torvalds  * Creates an info entry with the given file name and initializes as
6781da177e4SLinus Torvalds  * the default state.
6791da177e4SLinus Torvalds  *
6801da177e4SLinus Torvalds  * Usually called from other functions such as
6811da177e4SLinus Torvalds  * snd_info_create_card_entry().
6821da177e4SLinus Torvalds  *
683eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
6841da177e4SLinus Torvalds  */
6858e7ccb7bSTakashi Iwai static struct snd_info_entry *
snd_info_create_entry(const char * name,struct snd_info_entry * parent,struct module * module)686a858ee66STakashi Iwai snd_info_create_entry(const char *name, struct snd_info_entry *parent,
687a858ee66STakashi Iwai 		      struct module *module)
6881da177e4SLinus Torvalds {
68924c1f931STakashi Iwai 	struct snd_info_entry *entry;
690ca2c0966STakashi Iwai 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
6911da177e4SLinus Torvalds 	if (entry == NULL)
6921da177e4SLinus Torvalds 		return NULL;
693543537bdSPaulo Marques 	entry->name = kstrdup(name, GFP_KERNEL);
6941da177e4SLinus Torvalds 	if (entry->name == NULL) {
6951da177e4SLinus Torvalds 		kfree(entry);
6961da177e4SLinus Torvalds 		return NULL;
6971da177e4SLinus Torvalds 	}
6986a73cf46SJoe Perches 	entry->mode = S_IFREG | 0444;
6991da177e4SLinus Torvalds 	entry->content = SNDRV_INFO_CONTENT_TEXT;
7001a60d4c5SIngo Molnar 	mutex_init(&entry->access);
701746d4a02STakashi Iwai 	INIT_LIST_HEAD(&entry->children);
702746d4a02STakashi Iwai 	INIT_LIST_HEAD(&entry->list);
7038e7ccb7bSTakashi Iwai 	entry->parent = parent;
704a858ee66STakashi Iwai 	entry->module = module;
7058c2f8708STakashi Iwai 	if (parent) {
7068c2f8708STakashi Iwai 		mutex_lock(&parent->access);
7078e7ccb7bSTakashi Iwai 		list_add_tail(&entry->list, &parent->children);
7088c2f8708STakashi Iwai 		mutex_unlock(&parent->access);
7098c2f8708STakashi Iwai 	}
7101da177e4SLinus Torvalds 	return entry;
7111da177e4SLinus Torvalds }
7121da177e4SLinus Torvalds 
7131da177e4SLinus Torvalds /**
7141da177e4SLinus Torvalds  * snd_info_create_module_entry - create an info entry for the given module
7151da177e4SLinus Torvalds  * @module: the module pointer
7161da177e4SLinus Torvalds  * @name: the file name
7171da177e4SLinus Torvalds  * @parent: the parent directory
7181da177e4SLinus Torvalds  *
7191da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given module.
7201da177e4SLinus Torvalds  *
721eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
7221da177e4SLinus Torvalds  */
snd_info_create_module_entry(struct module * module,const char * name,struct snd_info_entry * parent)72324c1f931STakashi Iwai struct snd_info_entry *snd_info_create_module_entry(struct module * module,
7241da177e4SLinus Torvalds 					       const char *name,
72524c1f931STakashi Iwai 					       struct snd_info_entry *parent)
7261da177e4SLinus Torvalds {
7273a554371STakashi Iwai 	if (!parent)
7283a554371STakashi Iwai 		parent = snd_proc_root;
729a858ee66STakashi Iwai 	return snd_info_create_entry(name, parent, module);
7301da177e4SLinus Torvalds }
731c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_create_module_entry);
732c0d3fb39STakashi Iwai 
7331da177e4SLinus Torvalds /**
7341da177e4SLinus Torvalds  * snd_info_create_card_entry - create an info entry for the given card
7351da177e4SLinus Torvalds  * @card: the card instance
7361da177e4SLinus Torvalds  * @name: the file name
7371da177e4SLinus Torvalds  * @parent: the parent directory
7381da177e4SLinus Torvalds  *
7391da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given card.
7401da177e4SLinus Torvalds  *
741eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
7421da177e4SLinus Torvalds  */
snd_info_create_card_entry(struct snd_card * card,const char * name,struct snd_info_entry * parent)74324c1f931STakashi Iwai struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card,
7441da177e4SLinus Torvalds 					     const char *name,
74524c1f931STakashi Iwai 					     struct snd_info_entry * parent)
7461da177e4SLinus Torvalds {
7473a554371STakashi Iwai 	if (!parent)
7483a554371STakashi Iwai 		parent = card->proc_root;
749a858ee66STakashi Iwai 	return snd_info_create_entry(name, parent, card->module);
7501da177e4SLinus Torvalds }
751c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_create_card_entry);
752c0d3fb39STakashi Iwai 
snd_info_clear_entries(struct snd_info_entry * entry)753*2dac108bSTakashi Iwai static void snd_info_clear_entries(struct snd_info_entry *entry)
754746d4a02STakashi Iwai {
75590a409aaSTakashi Iwai 	struct snd_info_entry *p;
756746d4a02STakashi Iwai 
757746d4a02STakashi Iwai 	if (!entry->p)
758746d4a02STakashi Iwai 		return;
75990a409aaSTakashi Iwai 	list_for_each_entry(p, &entry->children, list)
760*2dac108bSTakashi Iwai 		snd_info_clear_entries(p);
761746d4a02STakashi Iwai 	entry->p = NULL;
762746d4a02STakashi Iwai }
763746d4a02STakashi Iwai 
7641da177e4SLinus Torvalds /**
7651da177e4SLinus Torvalds  * snd_info_free_entry - release the info entry
7661da177e4SLinus Torvalds  * @entry: the info entry
7671da177e4SLinus Torvalds  *
768c560a679STakashi Iwai  * Releases the info entry.
7691da177e4SLinus Torvalds  */
snd_info_free_entry(struct snd_info_entry * entry)77024c1f931STakashi Iwai void snd_info_free_entry(struct snd_info_entry * entry)
7711da177e4SLinus Torvalds {
772c560a679STakashi Iwai 	struct snd_info_entry *p, *n;
773c560a679STakashi Iwai 
774c560a679STakashi Iwai 	if (!entry)
7751da177e4SLinus Torvalds 		return;
776746d4a02STakashi Iwai 	if (entry->p) {
777*2dac108bSTakashi Iwai 		proc_remove(entry->p);
778746d4a02STakashi Iwai 		mutex_lock(&info_mutex);
779*2dac108bSTakashi Iwai 		snd_info_clear_entries(entry);
780746d4a02STakashi Iwai 		mutex_unlock(&info_mutex);
781746d4a02STakashi Iwai 	}
782c560a679STakashi Iwai 
783c560a679STakashi Iwai 	/* free all children at first */
784c560a679STakashi Iwai 	list_for_each_entry_safe(p, n, &entry->children, list)
785c560a679STakashi Iwai 		snd_info_free_entry(p);
786c560a679STakashi Iwai 
7878c2f8708STakashi Iwai 	p = entry->parent;
7888c2f8708STakashi Iwai 	if (p) {
7898c2f8708STakashi Iwai 		mutex_lock(&p->access);
79090a409aaSTakashi Iwai 		list_del(&entry->list);
7918c2f8708STakashi Iwai 		mutex_unlock(&p->access);
7928c2f8708STakashi Iwai 	}
7931da177e4SLinus Torvalds 	kfree(entry->name);
7941da177e4SLinus Torvalds 	if (entry->private_free)
7951da177e4SLinus Torvalds 		entry->private_free(entry);
7961da177e4SLinus Torvalds 	kfree(entry);
7971da177e4SLinus Torvalds }
798c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_free_entry);
799c0d3fb39STakashi Iwai 
__snd_info_register(struct snd_info_entry * entry)800348c5ad5STakashi Iwai static int __snd_info_register(struct snd_info_entry *entry)
8011da177e4SLinus Torvalds {
8021da177e4SLinus Torvalds 	struct proc_dir_entry *root, *p = NULL;
8031da177e4SLinus Torvalds 
8047eaa943cSTakashi Iwai 	if (snd_BUG_ON(!entry))
8057eaa943cSTakashi Iwai 		return -ENXIO;
806644dbd64STakashi Iwai 	root = entry->parent == NULL ? snd_proc_root->p : entry->parent->p;
8071a60d4c5SIngo Molnar 	mutex_lock(&info_mutex);
808348c5ad5STakashi Iwai 	if (entry->p || !root)
809348c5ad5STakashi Iwai 		goto unlock;
810aee0c612SAl Viro 	if (S_ISDIR(entry->mode)) {
811aee0c612SAl Viro 		p = proc_mkdir_mode(entry->name, entry->mode, root);
8121da177e4SLinus Torvalds 		if (!p) {
8131a60d4c5SIngo Molnar 			mutex_unlock(&info_mutex);
8141da177e4SLinus Torvalds 			return -ENOMEM;
8151da177e4SLinus Torvalds 		}
816aee0c612SAl Viro 	} else {
81797a32539SAlexey Dobriyan 		const struct proc_ops *ops;
8184adb7bcbSTakashi Iwai 		if (entry->content == SNDRV_INFO_CONTENT_DATA)
8194adb7bcbSTakashi Iwai 			ops = &snd_info_entry_operations;
8204adb7bcbSTakashi Iwai 		else
8214adb7bcbSTakashi Iwai 			ops = &snd_info_text_entry_ops;
822aee0c612SAl Viro 		p = proc_create_data(entry->name, entry->mode, root,
8234adb7bcbSTakashi Iwai 				     ops, entry);
824aee0c612SAl Viro 		if (!p) {
825aee0c612SAl Viro 			mutex_unlock(&info_mutex);
826aee0c612SAl Viro 			return -ENOMEM;
827aee0c612SAl Viro 		}
828271a15eaSDavid Howells 		proc_set_size(p, entry->size);
829aee0c612SAl Viro 	}
8301da177e4SLinus Torvalds 	entry->p = p;
831348c5ad5STakashi Iwai  unlock:
8321a60d4c5SIngo Molnar 	mutex_unlock(&info_mutex);
8331da177e4SLinus Torvalds 	return 0;
8341da177e4SLinus Torvalds }
835348c5ad5STakashi Iwai 
836348c5ad5STakashi Iwai /**
837348c5ad5STakashi Iwai  * snd_info_register - register the info entry
838348c5ad5STakashi Iwai  * @entry: the info entry
839348c5ad5STakashi Iwai  *
840348c5ad5STakashi Iwai  * Registers the proc info entry.
841348c5ad5STakashi Iwai  * The all children entries are registered recursively.
842348c5ad5STakashi Iwai  *
843348c5ad5STakashi Iwai  * Return: Zero if successful, or a negative error code on failure.
844348c5ad5STakashi Iwai  */
snd_info_register(struct snd_info_entry * entry)845348c5ad5STakashi Iwai int snd_info_register(struct snd_info_entry *entry)
846348c5ad5STakashi Iwai {
847348c5ad5STakashi Iwai 	struct snd_info_entry *p;
848348c5ad5STakashi Iwai 	int err;
849348c5ad5STakashi Iwai 
850348c5ad5STakashi Iwai 	if (!entry->p) {
851348c5ad5STakashi Iwai 		err = __snd_info_register(entry);
852348c5ad5STakashi Iwai 		if (err < 0)
853348c5ad5STakashi Iwai 			return err;
854348c5ad5STakashi Iwai 	}
855348c5ad5STakashi Iwai 
856348c5ad5STakashi Iwai 	list_for_each_entry(p, &entry->children, list) {
857348c5ad5STakashi Iwai 		err = snd_info_register(p);
858348c5ad5STakashi Iwai 		if (err < 0)
859348c5ad5STakashi Iwai 			return err;
860348c5ad5STakashi Iwai 	}
861348c5ad5STakashi Iwai 
862348c5ad5STakashi Iwai 	return 0;
863348c5ad5STakashi Iwai }
864c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_register);
865c0d3fb39STakashi Iwai 
8667453e1daSTakashi Iwai /**
8677453e1daSTakashi Iwai  * snd_card_rw_proc_new - Create a read/write text proc file entry for the card
8687453e1daSTakashi Iwai  * @card: the card instance
8697453e1daSTakashi Iwai  * @name: the file name
8707453e1daSTakashi Iwai  * @private_data: the arbitrary private data
8717453e1daSTakashi Iwai  * @read: the read callback
8727453e1daSTakashi Iwai  * @write: the write callback, NULL for read-only
8737453e1daSTakashi Iwai  *
8747453e1daSTakashi Iwai  * This proc file entry will be registered via snd_card_register() call, and
8757453e1daSTakashi Iwai  * it will be removed automatically at the card removal, too.
876281dee67STakashi Iwai  *
877281dee67STakashi Iwai  * Return: zero if successful, or a negative error code
8787453e1daSTakashi Iwai  */
snd_card_rw_proc_new(struct snd_card * card,const char * name,void * private_data,void (* read)(struct snd_info_entry *,struct snd_info_buffer *),void (* write)(struct snd_info_entry * entry,struct snd_info_buffer * buffer))8797453e1daSTakashi Iwai int snd_card_rw_proc_new(struct snd_card *card, const char *name,
8807453e1daSTakashi Iwai 			 void *private_data,
8817453e1daSTakashi Iwai 			 void (*read)(struct snd_info_entry *,
8827453e1daSTakashi Iwai 				      struct snd_info_buffer *),
8837453e1daSTakashi Iwai 			 void (*write)(struct snd_info_entry *entry,
8847453e1daSTakashi Iwai 				       struct snd_info_buffer *buffer))
8857453e1daSTakashi Iwai {
8867453e1daSTakashi Iwai 	struct snd_info_entry *entry;
8877453e1daSTakashi Iwai 
8887453e1daSTakashi Iwai 	entry = snd_info_create_card_entry(card, name, card->proc_root);
8897453e1daSTakashi Iwai 	if (!entry)
8907453e1daSTakashi Iwai 		return -ENOMEM;
8917453e1daSTakashi Iwai 	snd_info_set_text_ops(entry, private_data, read);
8927453e1daSTakashi Iwai 	if (write) {
8937453e1daSTakashi Iwai 		entry->mode |= 0200;
8947453e1daSTakashi Iwai 		entry->c.text.write = write;
8957453e1daSTakashi Iwai 	}
8967453e1daSTakashi Iwai 	return 0;
8977453e1daSTakashi Iwai }
8987453e1daSTakashi Iwai EXPORT_SYMBOL_GPL(snd_card_rw_proc_new);
8997453e1daSTakashi Iwai 
9001da177e4SLinus Torvalds /*
9011da177e4SLinus Torvalds 
9021da177e4SLinus Torvalds  */
9031da177e4SLinus Torvalds 
snd_info_version_read(struct snd_info_entry * entry,struct snd_info_buffer * buffer)90424c1f931STakashi Iwai static void snd_info_version_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
9051da177e4SLinus Torvalds {
9061da177e4SLinus Torvalds 	snd_iprintf(buffer,
90742662748SJaroslav Kysela 		    "Advanced Linux Sound Architecture Driver Version k%s.\n",
90842662748SJaroslav Kysela 		    init_utsname()->release);
9091da177e4SLinus Torvalds }
9101da177e4SLinus Torvalds 
snd_info_version_init(void)9111da177e4SLinus Torvalds static int __init snd_info_version_init(void)
9121da177e4SLinus Torvalds {
91324c1f931STakashi Iwai 	struct snd_info_entry *entry;
9141da177e4SLinus Torvalds 
9151da177e4SLinus Torvalds 	entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL);
9161da177e4SLinus Torvalds 	if (entry == NULL)
9171da177e4SLinus Torvalds 		return -ENOMEM;
9181da177e4SLinus Torvalds 	entry->c.text.read = snd_info_version_read;
919b591b6e9STakashi Iwai 	return snd_info_register(entry); /* freed in error path */
9201da177e4SLinus Torvalds }
921