xref: /linux/sound/core/info.c (revision 79790b6818e96c58fe2bffee1b418c16e64e7b80)
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);
59c7a60651STakashi 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;
108*4b72362bSTakashi Iwai 	loff_t size;
1091da177e4SLinus Torvalds 
1101da177e4SLinus Torvalds 	data = file->private_data;
1111da177e4SLinus Torvalds 	entry = data->entry;
112*4b72362bSTakashi Iwai 	guard(mutex)(&entry->access);
113*4b72362bSTakashi Iwai 	if (entry->c.ops->llseek)
114*4b72362bSTakashi Iwai 		return entry->c.ops->llseek(entry,
1151da177e4SLinus Torvalds 					    data->file_private_data,
1161da177e4SLinus Torvalds 					    file, offset, orig);
1174adb7bcbSTakashi Iwai 
11873029e0fSTakashi Iwai 	size = entry->size;
11973029e0fSTakashi Iwai 	switch (orig) {
12073029e0fSTakashi Iwai 	case SEEK_SET:
1211da177e4SLinus Torvalds 		break;
12273029e0fSTakashi Iwai 	case SEEK_CUR:
12373029e0fSTakashi Iwai 		offset += file->f_pos;
12473029e0fSTakashi Iwai 		break;
12573029e0fSTakashi Iwai 	case SEEK_END:
12673029e0fSTakashi Iwai 		if (!size)
127*4b72362bSTakashi Iwai 			return -EINVAL;
12873029e0fSTakashi Iwai 		offset += size;
12973029e0fSTakashi Iwai 		break;
13073029e0fSTakashi Iwai 	default:
131*4b72362bSTakashi Iwai 		return -EINVAL;
1321da177e4SLinus Torvalds 	}
13373029e0fSTakashi Iwai 	if (offset < 0)
134*4b72362bSTakashi Iwai 		return -EINVAL;
13573029e0fSTakashi Iwai 	if (size && offset > size)
13673029e0fSTakashi Iwai 		offset = size;
13773029e0fSTakashi Iwai 	file->f_pos = offset;
138*4b72362bSTakashi Iwai 	return offset;
1391da177e4SLinus Torvalds }
1401da177e4SLinus Torvalds 
snd_info_entry_read(struct file * file,char __user * buffer,size_t count,loff_t * offset)1411da177e4SLinus Torvalds static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
1421da177e4SLinus Torvalds 				   size_t count, loff_t * offset)
1431da177e4SLinus Torvalds {
1444adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1454adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
1464adb7bcbSTakashi Iwai 	size_t size;
1471da177e4SLinus Torvalds 	loff_t pos;
1481da177e4SLinus Torvalds 
1491da177e4SLinus Torvalds 	pos = *offset;
1504adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
1511da177e4SLinus Torvalds 		return -EIO;
152d97e1b78STakashi Iwai 	if (pos >= entry->size)
153d97e1b78STakashi Iwai 		return 0;
154d97e1b78STakashi Iwai 	size = entry->size - pos;
155d97e1b78STakashi Iwai 	size = min(count, size);
1564adb7bcbSTakashi Iwai 	size = entry->c.ops->read(entry, data->file_private_data,
157d97e1b78STakashi Iwai 				  file, buffer, size, pos);
1581da177e4SLinus Torvalds 	if ((ssize_t) size > 0)
1591da177e4SLinus Torvalds 		*offset = pos + size;
1601da177e4SLinus Torvalds 	return size;
1611da177e4SLinus Torvalds }
1621da177e4SLinus Torvalds 
snd_info_entry_write(struct file * file,const char __user * buffer,size_t count,loff_t * offset)1631da177e4SLinus Torvalds static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer,
1641da177e4SLinus Torvalds 				    size_t count, loff_t * offset)
1651da177e4SLinus Torvalds {
1664adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1674adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
1687e4eeec8STakashi Iwai 	ssize_t size = 0;
1691da177e4SLinus Torvalds 	loff_t pos;
1701da177e4SLinus Torvalds 
1711da177e4SLinus Torvalds 	pos = *offset;
1724adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
1731da177e4SLinus Torvalds 		return -EIO;
1744adb7bcbSTakashi Iwai 	if (count > 0) {
175d97e1b78STakashi Iwai 		size_t maxsize = entry->size - pos;
176d97e1b78STakashi Iwai 		count = min(count, maxsize);
1774adb7bcbSTakashi Iwai 		size = entry->c.ops->write(entry, data->file_private_data,
1781da177e4SLinus Torvalds 					   file, buffer, count, pos);
179d97e1b78STakashi Iwai 	}
1804adb7bcbSTakashi Iwai 	if (size > 0)
1811da177e4SLinus Torvalds 		*offset = pos + size;
1821da177e4SLinus Torvalds 	return size;
1831da177e4SLinus Torvalds }
1841da177e4SLinus Torvalds 
snd_info_entry_poll(struct file * file,poll_table * wait)185680ef72aSAl Viro static __poll_t snd_info_entry_poll(struct file *file, poll_table *wait)
1861da177e4SLinus Torvalds {
1874adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1884adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
189680ef72aSAl Viro 	__poll_t mask = 0;
1901da177e4SLinus Torvalds 
1911da177e4SLinus Torvalds 	if (entry->c.ops->poll)
1921da177e4SLinus Torvalds 		return entry->c.ops->poll(entry,
1931da177e4SLinus Torvalds 					  data->file_private_data,
1941da177e4SLinus Torvalds 					  file, wait);
1951da177e4SLinus Torvalds 	if (entry->c.ops->read)
196a9a08845SLinus Torvalds 		mask |= EPOLLIN | EPOLLRDNORM;
1971da177e4SLinus Torvalds 	if (entry->c.ops->write)
198a9a08845SLinus Torvalds 		mask |= EPOLLOUT | EPOLLWRNORM;
1991da177e4SLinus Torvalds 	return mask;
2001da177e4SLinus Torvalds }
2011da177e4SLinus Torvalds 
snd_info_entry_ioctl(struct file * file,unsigned int cmd,unsigned long arg)202d99e9889SIngo Molnar static long snd_info_entry_ioctl(struct file *file, unsigned int cmd,
203d99e9889SIngo Molnar 				unsigned long arg)
2041da177e4SLinus Torvalds {
2054adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2064adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2071da177e4SLinus Torvalds 
2084adb7bcbSTakashi Iwai 	if (!entry->c.ops->ioctl)
2091da177e4SLinus Torvalds 		return -ENOTTY;
2104adb7bcbSTakashi Iwai 	return entry->c.ops->ioctl(entry, data->file_private_data,
2114adb7bcbSTakashi Iwai 				   file, cmd, arg);
2121da177e4SLinus Torvalds }
2131da177e4SLinus Torvalds 
snd_info_entry_mmap(struct file * file,struct vm_area_struct * vma)2141da177e4SLinus Torvalds static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma)
2151da177e4SLinus Torvalds {
216496ad9aaSAl Viro 	struct inode *inode = file_inode(file);
21724c1f931STakashi Iwai 	struct snd_info_private_data *data;
2181da177e4SLinus Torvalds 	struct snd_info_entry *entry;
2191da177e4SLinus Torvalds 
2201da177e4SLinus Torvalds 	data = file->private_data;
2211da177e4SLinus Torvalds 	if (data == NULL)
2221da177e4SLinus Torvalds 		return 0;
2231da177e4SLinus Torvalds 	entry = data->entry;
2244adb7bcbSTakashi Iwai 	if (!entry->c.ops->mmap)
2251da177e4SLinus Torvalds 		return -ENXIO;
2264adb7bcbSTakashi Iwai 	return entry->c.ops->mmap(entry, data->file_private_data,
2274adb7bcbSTakashi Iwai 				  inode, file, vma);
2284adb7bcbSTakashi Iwai }
2294adb7bcbSTakashi Iwai 
snd_info_entry_open(struct inode * inode,struct file * file)2304adb7bcbSTakashi Iwai static int snd_info_entry_open(struct inode *inode, struct file *file)
2314adb7bcbSTakashi Iwai {
232359745d7SMuchun Song 	struct snd_info_entry *entry = pde_data(inode);
2334adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
2344adb7bcbSTakashi Iwai 	int mode, err;
2354adb7bcbSTakashi Iwai 
236*4b72362bSTakashi Iwai 	guard(mutex)(&info_mutex);
2374adb7bcbSTakashi Iwai 	err = alloc_info_private(entry, &data);
2384adb7bcbSTakashi Iwai 	if (err < 0)
239*4b72362bSTakashi Iwai 		return err;
2404adb7bcbSTakashi Iwai 
2414adb7bcbSTakashi Iwai 	mode = file->f_flags & O_ACCMODE;
2424adb7bcbSTakashi Iwai 	if (((mode == O_RDONLY || mode == O_RDWR) && !entry->c.ops->read) ||
2434adb7bcbSTakashi Iwai 	    ((mode == O_WRONLY || mode == O_RDWR) && !entry->c.ops->write)) {
2444adb7bcbSTakashi Iwai 		err = -ENODEV;
2454adb7bcbSTakashi Iwai 		goto error;
2464adb7bcbSTakashi Iwai 	}
2474adb7bcbSTakashi Iwai 
2484adb7bcbSTakashi Iwai 	if (entry->c.ops->open) {
2494adb7bcbSTakashi Iwai 		err = entry->c.ops->open(entry, mode, &data->file_private_data);
2504adb7bcbSTakashi Iwai 		if (err < 0)
2514adb7bcbSTakashi Iwai 			goto error;
2524adb7bcbSTakashi Iwai 	}
2534adb7bcbSTakashi Iwai 
2544adb7bcbSTakashi Iwai 	file->private_data = data;
2554adb7bcbSTakashi Iwai 	return 0;
2564adb7bcbSTakashi Iwai 
2574adb7bcbSTakashi Iwai  error:
2584adb7bcbSTakashi Iwai 	kfree(data);
2594adb7bcbSTakashi Iwai 	module_put(entry->module);
2604adb7bcbSTakashi Iwai 	return err;
2614adb7bcbSTakashi Iwai }
2624adb7bcbSTakashi Iwai 
snd_info_entry_release(struct inode * inode,struct file * file)2634adb7bcbSTakashi Iwai static int snd_info_entry_release(struct inode *inode, struct file *file)
2644adb7bcbSTakashi Iwai {
2654adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2664adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2674adb7bcbSTakashi Iwai 
2684adb7bcbSTakashi Iwai 	if (entry->c.ops->release)
2694adb7bcbSTakashi Iwai 		entry->c.ops->release(entry, file->f_flags & O_ACCMODE,
2704adb7bcbSTakashi Iwai 				      data->file_private_data);
2714adb7bcbSTakashi Iwai 	module_put(entry->module);
2724adb7bcbSTakashi Iwai 	kfree(data);
2734adb7bcbSTakashi Iwai 	return 0;
2741da177e4SLinus Torvalds }
2751da177e4SLinus Torvalds 
27697a32539SAlexey Dobriyan static const struct proc_ops snd_info_entry_operations =
2771da177e4SLinus Torvalds {
27897a32539SAlexey Dobriyan 	.proc_lseek	= snd_info_entry_llseek,
27997a32539SAlexey Dobriyan 	.proc_read	= snd_info_entry_read,
28097a32539SAlexey Dobriyan 	.proc_write	= snd_info_entry_write,
28197a32539SAlexey Dobriyan 	.proc_poll	= snd_info_entry_poll,
28297a32539SAlexey Dobriyan 	.proc_ioctl	= snd_info_entry_ioctl,
28397a32539SAlexey Dobriyan 	.proc_mmap	= snd_info_entry_mmap,
28497a32539SAlexey Dobriyan 	.proc_open	= snd_info_entry_open,
28597a32539SAlexey Dobriyan 	.proc_release	= snd_info_entry_release,
2861da177e4SLinus Torvalds };
2871da177e4SLinus Torvalds 
2884adb7bcbSTakashi Iwai /*
2894adb7bcbSTakashi Iwai  * file ops for text proc files
2904adb7bcbSTakashi Iwai  */
snd_info_text_entry_write(struct file * file,const char __user * buffer,size_t count,loff_t * offset)2914adb7bcbSTakashi Iwai static ssize_t snd_info_text_entry_write(struct file *file,
2924adb7bcbSTakashi Iwai 					 const char __user *buffer,
2934adb7bcbSTakashi Iwai 					 size_t count, loff_t *offset)
2944adb7bcbSTakashi Iwai {
2954adb7bcbSTakashi Iwai 	struct seq_file *m = file->private_data;
2964adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = m->private;
2974adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2984adb7bcbSTakashi Iwai 	struct snd_info_buffer *buf;
2994adb7bcbSTakashi Iwai 	loff_t pos;
3004adb7bcbSTakashi Iwai 	size_t next;
3014adb7bcbSTakashi Iwai 
3026809cd68STakashi Iwai 	if (!entry->c.text.write)
3036809cd68STakashi Iwai 		return -EIO;
3044adb7bcbSTakashi Iwai 	pos = *offset;
3054adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
3064adb7bcbSTakashi Iwai 		return -EIO;
3074adb7bcbSTakashi Iwai 	next = pos + count;
308027a9fe6STakashi Iwai 	/* don't handle too large text inputs */
309027a9fe6STakashi Iwai 	if (next > 16 * 1024)
310027a9fe6STakashi Iwai 		return -EIO;
311*4b72362bSTakashi Iwai 	guard(mutex)(&entry->access);
3124adb7bcbSTakashi Iwai 	buf = data->wbuffer;
3134adb7bcbSTakashi Iwai 	if (!buf) {
3144adb7bcbSTakashi Iwai 		data->wbuffer = buf = kzalloc(sizeof(*buf), GFP_KERNEL);
315*4b72362bSTakashi Iwai 		if (!buf)
316*4b72362bSTakashi Iwai 			return -ENOMEM;
3174adb7bcbSTakashi Iwai 	}
3184adb7bcbSTakashi Iwai 	if (next > buf->len) {
319ffb73b08STakashi Iwai 		char *nbuf = kvzalloc(PAGE_ALIGN(next), GFP_KERNEL);
320*4b72362bSTakashi Iwai 		if (!nbuf)
321*4b72362bSTakashi Iwai 			return -ENOMEM;
322ffb73b08STakashi Iwai 		kvfree(buf->buffer);
3234adb7bcbSTakashi Iwai 		buf->buffer = nbuf;
3244adb7bcbSTakashi Iwai 		buf->len = PAGE_ALIGN(next);
3254adb7bcbSTakashi Iwai 	}
326*4b72362bSTakashi Iwai 	if (copy_from_user(buf->buffer + pos, buffer, count))
327*4b72362bSTakashi Iwai 		return -EFAULT;
3284adb7bcbSTakashi Iwai 	buf->size = next;
3294adb7bcbSTakashi Iwai 	*offset = next;
3304adb7bcbSTakashi Iwai 	return count;
3314adb7bcbSTakashi Iwai }
3324adb7bcbSTakashi Iwai 
snd_info_seq_show(struct seq_file * seq,void * p)3334adb7bcbSTakashi Iwai static int snd_info_seq_show(struct seq_file *seq, void *p)
3344adb7bcbSTakashi Iwai {
3354adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = seq->private;
3364adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
3374adb7bcbSTakashi Iwai 
3386809cd68STakashi Iwai 	if (!entry->c.text.read) {
3396809cd68STakashi Iwai 		return -EIO;
3406809cd68STakashi Iwai 	} else {
3414adb7bcbSTakashi Iwai 		data->rbuffer->buffer = (char *)seq; /* XXX hack! */
3424adb7bcbSTakashi Iwai 		entry->c.text.read(entry, data->rbuffer);
3434adb7bcbSTakashi Iwai 	}
3444adb7bcbSTakashi Iwai 	return 0;
3454adb7bcbSTakashi Iwai }
3464adb7bcbSTakashi Iwai 
snd_info_text_entry_open(struct inode * inode,struct file * file)3474adb7bcbSTakashi Iwai static int snd_info_text_entry_open(struct inode *inode, struct file *file)
3484adb7bcbSTakashi Iwai {
349359745d7SMuchun Song 	struct snd_info_entry *entry = pde_data(inode);
3504adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
3514adb7bcbSTakashi Iwai 	int err;
3524adb7bcbSTakashi Iwai 
353*4b72362bSTakashi Iwai 	guard(mutex)(&info_mutex);
3544adb7bcbSTakashi Iwai 	err = alloc_info_private(entry, &data);
3554adb7bcbSTakashi Iwai 	if (err < 0)
356*4b72362bSTakashi Iwai 		return err;
3574adb7bcbSTakashi Iwai 
3584adb7bcbSTakashi Iwai 	data->rbuffer = kzalloc(sizeof(*data->rbuffer), GFP_KERNEL);
3594adb7bcbSTakashi Iwai 	if (!data->rbuffer) {
3604adb7bcbSTakashi Iwai 		err = -ENOMEM;
3614adb7bcbSTakashi Iwai 		goto error;
3624adb7bcbSTakashi Iwai 	}
3634adb7bcbSTakashi Iwai 	if (entry->size)
3644adb7bcbSTakashi Iwai 		err = single_open_size(file, snd_info_seq_show, data,
3654adb7bcbSTakashi Iwai 				       entry->size);
3664adb7bcbSTakashi Iwai 	else
3674adb7bcbSTakashi Iwai 		err = single_open(file, snd_info_seq_show, data);
3684adb7bcbSTakashi Iwai 	if (err < 0)
3694adb7bcbSTakashi Iwai 		goto error;
3704adb7bcbSTakashi Iwai 	return 0;
3714adb7bcbSTakashi Iwai 
3724adb7bcbSTakashi Iwai  error:
3734adb7bcbSTakashi Iwai 	kfree(data->rbuffer);
3744adb7bcbSTakashi Iwai 	kfree(data);
3754adb7bcbSTakashi Iwai 	module_put(entry->module);
3764adb7bcbSTakashi Iwai 	return err;
3774adb7bcbSTakashi Iwai }
3784adb7bcbSTakashi Iwai 
snd_info_text_entry_release(struct inode * inode,struct file * file)3794adb7bcbSTakashi Iwai static int snd_info_text_entry_release(struct inode *inode, struct file *file)
3804adb7bcbSTakashi Iwai {
3814adb7bcbSTakashi Iwai 	struct seq_file *m = file->private_data;
3824adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = m->private;
3834adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
3844adb7bcbSTakashi Iwai 
3854adb7bcbSTakashi Iwai 	if (data->wbuffer && entry->c.text.write)
3864adb7bcbSTakashi Iwai 		entry->c.text.write(entry, data->wbuffer);
3874adb7bcbSTakashi Iwai 
3884adb7bcbSTakashi Iwai 	single_release(inode, file);
3894adb7bcbSTakashi Iwai 	kfree(data->rbuffer);
3904adb7bcbSTakashi Iwai 	if (data->wbuffer) {
391ffb73b08STakashi Iwai 		kvfree(data->wbuffer->buffer);
3924adb7bcbSTakashi Iwai 		kfree(data->wbuffer);
3934adb7bcbSTakashi Iwai 	}
3944adb7bcbSTakashi Iwai 
3954adb7bcbSTakashi Iwai 	module_put(entry->module);
3964adb7bcbSTakashi Iwai 	kfree(data);
3974adb7bcbSTakashi Iwai 	return 0;
3984adb7bcbSTakashi Iwai }
3994adb7bcbSTakashi Iwai 
40097a32539SAlexey Dobriyan static const struct proc_ops snd_info_text_entry_ops =
4014adb7bcbSTakashi Iwai {
40297a32539SAlexey Dobriyan 	.proc_open	= snd_info_text_entry_open,
40397a32539SAlexey Dobriyan 	.proc_release	= snd_info_text_entry_release,
40497a32539SAlexey Dobriyan 	.proc_write	= snd_info_text_entry_write,
40597a32539SAlexey Dobriyan 	.proc_lseek	= seq_lseek,
40697a32539SAlexey Dobriyan 	.proc_read	= seq_read,
4074adb7bcbSTakashi Iwai };
4084adb7bcbSTakashi Iwai 
create_subdir(struct module * mod,const char * name)409886364f6STakashi Iwai static struct snd_info_entry *create_subdir(struct module *mod,
410886364f6STakashi Iwai 					    const char *name)
411886364f6STakashi Iwai {
412886364f6STakashi Iwai 	struct snd_info_entry *entry;
413886364f6STakashi Iwai 
414886364f6STakashi Iwai 	entry = snd_info_create_module_entry(mod, name, NULL);
415886364f6STakashi Iwai 	if (!entry)
416886364f6STakashi Iwai 		return NULL;
4176a73cf46SJoe Perches 	entry->mode = S_IFDIR | 0555;
418886364f6STakashi Iwai 	if (snd_info_register(entry) < 0) {
419886364f6STakashi Iwai 		snd_info_free_entry(entry);
420886364f6STakashi Iwai 		return NULL;
421886364f6STakashi Iwai 	}
422886364f6STakashi Iwai 	return entry;
423886364f6STakashi Iwai }
424886364f6STakashi Iwai 
4258e7ccb7bSTakashi Iwai static struct snd_info_entry *
426a858ee66STakashi Iwai snd_info_create_entry(const char *name, struct snd_info_entry *parent,
427a858ee66STakashi Iwai 		      struct module *module);
428644dbd64STakashi Iwai 
snd_info_init(void)4291da177e4SLinus Torvalds int __init snd_info_init(void)
4301da177e4SLinus Torvalds {
431a858ee66STakashi Iwai 	snd_proc_root = snd_info_create_entry("asound", NULL, THIS_MODULE);
432644dbd64STakashi Iwai 	if (!snd_proc_root)
4331da177e4SLinus Torvalds 		return -ENOMEM;
4346a73cf46SJoe Perches 	snd_proc_root->mode = S_IFDIR | 0555;
435644dbd64STakashi Iwai 	snd_proc_root->p = proc_mkdir("asound", NULL);
436644dbd64STakashi Iwai 	if (!snd_proc_root->p)
437644dbd64STakashi Iwai 		goto error;
4381da177e4SLinus Torvalds #ifdef CONFIG_SND_OSSEMUL
439886364f6STakashi Iwai 	snd_oss_root = create_subdir(THIS_MODULE, "oss");
440886364f6STakashi Iwai 	if (!snd_oss_root)
441886364f6STakashi Iwai 		goto error;
4421da177e4SLinus Torvalds #endif
4438eeaa2f9STakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
444886364f6STakashi Iwai 	snd_seq_root = create_subdir(THIS_MODULE, "seq");
445886364f6STakashi Iwai 	if (!snd_seq_root)
446886364f6STakashi Iwai 		goto error;
4471da177e4SLinus Torvalds #endif
448b591b6e9STakashi Iwai 	if (snd_info_version_init() < 0 ||
449b591b6e9STakashi Iwai 	    snd_minor_info_init() < 0 ||
450b591b6e9STakashi Iwai 	    snd_minor_info_oss_init() < 0 ||
451a0dca822STakashi Iwai 	    snd_card_info_init() < 0 ||
452a0dca822STakashi Iwai 	    snd_info_minor_register() < 0)
453b591b6e9STakashi Iwai 		goto error;
4541da177e4SLinus Torvalds 	return 0;
455886364f6STakashi Iwai 
456886364f6STakashi Iwai  error:
457644dbd64STakashi Iwai 	snd_info_free_entry(snd_proc_root);
458886364f6STakashi Iwai 	return -ENOMEM;
4591da177e4SLinus Torvalds }
4601da177e4SLinus Torvalds 
snd_info_done(void)4611da177e4SLinus Torvalds int __exit snd_info_done(void)
4621da177e4SLinus Torvalds {
463644dbd64STakashi Iwai 	snd_info_free_entry(snd_proc_root);
4641da177e4SLinus Torvalds 	return 0;
4651da177e4SLinus Torvalds }
4661da177e4SLinus Torvalds 
snd_card_id_read(struct snd_info_entry * entry,struct snd_info_buffer * buffer)46729b2625fSTakashi Iwai static void snd_card_id_read(struct snd_info_entry *entry,
46829b2625fSTakashi Iwai 			     struct snd_info_buffer *buffer)
46929b2625fSTakashi Iwai {
47029b2625fSTakashi Iwai 	struct snd_card *card = entry->private_data;
47129b2625fSTakashi Iwai 
47229b2625fSTakashi Iwai 	snd_iprintf(buffer, "%s\n", card->id);
47329b2625fSTakashi Iwai }
47429b2625fSTakashi Iwai 
4751da177e4SLinus Torvalds /*
4761da177e4SLinus Torvalds  * create a card proc file
4771da177e4SLinus Torvalds  * called from init.c
4781da177e4SLinus Torvalds  */
snd_info_card_create(struct snd_card * card)47924c1f931STakashi Iwai int snd_info_card_create(struct snd_card *card)
4801da177e4SLinus Torvalds {
4811da177e4SLinus Torvalds 	char str[8];
48224c1f931STakashi Iwai 	struct snd_info_entry *entry;
4831da177e4SLinus Torvalds 
4847eaa943cSTakashi Iwai 	if (snd_BUG_ON(!card))
4857eaa943cSTakashi Iwai 		return -ENXIO;
4861da177e4SLinus Torvalds 
4871da177e4SLinus Torvalds 	sprintf(str, "card%i", card->number);
488886364f6STakashi Iwai 	entry = create_subdir(card->module, str);
489886364f6STakashi Iwai 	if (!entry)
4901da177e4SLinus Torvalds 		return -ENOMEM;
4911da177e4SLinus Torvalds 	card->proc_root = entry;
49229b2625fSTakashi Iwai 
49329b2625fSTakashi Iwai 	return snd_card_ro_proc_new(card, "id", card, snd_card_id_read);
4941da177e4SLinus Torvalds }
4951da177e4SLinus Torvalds 
4961da177e4SLinus Torvalds /*
4971da177e4SLinus Torvalds  * register the card proc file
4981da177e4SLinus Torvalds  * called from init.c
4992471b6c8STakashi Iwai  * can be called multiple times for reinitialization
5001da177e4SLinus Torvalds  */
snd_info_card_register(struct snd_card * card)50124c1f931STakashi Iwai int snd_info_card_register(struct snd_card *card)
5021da177e4SLinus Torvalds {
5031da177e4SLinus Torvalds 	struct proc_dir_entry *p;
5042471b6c8STakashi Iwai 	int err;
5051da177e4SLinus Torvalds 
5067eaa943cSTakashi Iwai 	if (snd_BUG_ON(!card))
5077eaa943cSTakashi Iwai 		return -ENXIO;
5081da177e4SLinus Torvalds 
509348c5ad5STakashi Iwai 	err = snd_info_register(card->proc_root);
5102471b6c8STakashi Iwai 	if (err < 0)
5112471b6c8STakashi Iwai 		return err;
5122471b6c8STakashi Iwai 
5131da177e4SLinus Torvalds 	if (!strcmp(card->id, card->proc_root->name))
5141da177e4SLinus Torvalds 		return 0;
5151da177e4SLinus Torvalds 
5162471b6c8STakashi Iwai 	if (card->proc_root_link)
5172471b6c8STakashi Iwai 		return 0;
518644dbd64STakashi Iwai 	p = proc_symlink(card->id, snd_proc_root->p, card->proc_root->name);
5192471b6c8STakashi Iwai 	if (!p)
5201da177e4SLinus Torvalds 		return -ENOMEM;
5211da177e4SLinus Torvalds 	card->proc_root_link = p;
5221da177e4SLinus Torvalds 	return 0;
5231da177e4SLinus Torvalds }
5241da177e4SLinus Torvalds 
5251da177e4SLinus Torvalds /*
526c2eb9c4eSJaroslav Kysela  * called on card->id change
527c2eb9c4eSJaroslav Kysela  */
snd_info_card_id_change(struct snd_card * card)528c2eb9c4eSJaroslav Kysela void snd_info_card_id_change(struct snd_card *card)
529c2eb9c4eSJaroslav Kysela {
530*4b72362bSTakashi Iwai 	guard(mutex)(&info_mutex);
531c2eb9c4eSJaroslav Kysela 	if (card->proc_root_link) {
532a8ca16eaSDavid Howells 		proc_remove(card->proc_root_link);
533c2eb9c4eSJaroslav Kysela 		card->proc_root_link = NULL;
534c2eb9c4eSJaroslav Kysela 	}
535c2eb9c4eSJaroslav Kysela 	if (strcmp(card->id, card->proc_root->name))
536c2eb9c4eSJaroslav Kysela 		card->proc_root_link = proc_symlink(card->id,
537644dbd64STakashi Iwai 						    snd_proc_root->p,
538c2eb9c4eSJaroslav Kysela 						    card->proc_root->name);
539c2eb9c4eSJaroslav Kysela }
540c2eb9c4eSJaroslav Kysela 
541c2eb9c4eSJaroslav Kysela /*
5421da177e4SLinus Torvalds  * de-register the card proc file
5431da177e4SLinus Torvalds  * called from init.c
5441da177e4SLinus Torvalds  */
snd_info_card_disconnect(struct snd_card * card)545746d4a02STakashi Iwai void snd_info_card_disconnect(struct snd_card *card)
5461da177e4SLinus Torvalds {
5477eaa943cSTakashi Iwai 	if (!card)
5487eaa943cSTakashi Iwai 		return;
549c7a60651STakashi Iwai 
550a8ca16eaSDavid Howells 	proc_remove(card->proc_root_link);
551746d4a02STakashi Iwai 	if (card->proc_root)
552c7a60651STakashi Iwai 		proc_remove(card->proc_root->p);
553c7a60651STakashi Iwai 
554*4b72362bSTakashi Iwai 	guard(mutex)(&info_mutex);
555c7a60651STakashi Iwai 	if (card->proc_root)
556c7a60651STakashi Iwai 		snd_info_clear_entries(card->proc_root);
557c7a60651STakashi Iwai 	card->proc_root_link = NULL;
558c7a60651STakashi Iwai 	card->proc_root = NULL;
5591da177e4SLinus Torvalds }
560746d4a02STakashi Iwai 
561746d4a02STakashi Iwai /*
562746d4a02STakashi Iwai  * release the card proc file resources
563746d4a02STakashi Iwai  * called from init.c
564746d4a02STakashi Iwai  */
snd_info_card_free(struct snd_card * card)565746d4a02STakashi Iwai int snd_info_card_free(struct snd_card *card)
566746d4a02STakashi Iwai {
5677eaa943cSTakashi Iwai 	if (!card)
5687eaa943cSTakashi Iwai 		return 0;
569746d4a02STakashi Iwai 	snd_info_free_entry(card->proc_root);
570746d4a02STakashi Iwai 	card->proc_root = NULL;
5711da177e4SLinus Torvalds 	return 0;
5721da177e4SLinus Torvalds }
5731da177e4SLinus Torvalds 
5741da177e4SLinus Torvalds 
5751da177e4SLinus Torvalds /**
5761da177e4SLinus Torvalds  * snd_info_get_line - read one line from the procfs buffer
5771da177e4SLinus Torvalds  * @buffer: the procfs buffer
5781da177e4SLinus Torvalds  * @line: the buffer to store
579ddc64b27SClemens Ladisch  * @len: the max. buffer size
5801da177e4SLinus Torvalds  *
5811da177e4SLinus Torvalds  * Reads one line from the buffer and stores the string.
5821da177e4SLinus Torvalds  *
583eb7c06e8SYacine Belkadi  * Return: Zero if successful, or 1 if error or EOF.
5841da177e4SLinus Torvalds  */
snd_info_get_line(struct snd_info_buffer * buffer,char * line,int len)58524c1f931STakashi Iwai int snd_info_get_line(struct snd_info_buffer *buffer, char *line, int len)
5861da177e4SLinus Torvalds {
5870e023687SColin Ian King 	int c;
5881da177e4SLinus Torvalds 
58960379ba0STakashi Iwai 	if (snd_BUG_ON(!buffer))
59060379ba0STakashi Iwai 		return 1;
59160379ba0STakashi Iwai 	if (!buffer->buffer)
5920bc0ec90STakashi Iwai 		return 1;
5931da177e4SLinus Torvalds 	if (len <= 0 || buffer->stop || buffer->error)
5941da177e4SLinus Torvalds 		return 1;
5950bc0ec90STakashi Iwai 	while (!buffer->stop) {
5967e4eeec8STakashi Iwai 		c = buffer->buffer[buffer->curr++];
5977e4eeec8STakashi Iwai 		if (buffer->curr >= buffer->size)
5981da177e4SLinus Torvalds 			buffer->stop = 1;
5990bc0ec90STakashi Iwai 		if (c == '\n')
6001da177e4SLinus Torvalds 			break;
601ddc64b27SClemens Ladisch 		if (len > 1) {
6020bc0ec90STakashi Iwai 			len--;
6031da177e4SLinus Torvalds 			*line++ = c;
6041da177e4SLinus Torvalds 		}
6051da177e4SLinus Torvalds 	}
6061da177e4SLinus Torvalds 	*line = '\0';
6071da177e4SLinus Torvalds 	return 0;
6081da177e4SLinus Torvalds }
609c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_get_line);
610c0d3fb39STakashi Iwai 
6111da177e4SLinus Torvalds /**
612856def8aSHenrik Kretzschmar  * snd_info_get_str - parse a string token
6131da177e4SLinus Torvalds  * @dest: the buffer to store the string token
6141da177e4SLinus Torvalds  * @src: the original string
6151da177e4SLinus Torvalds  * @len: the max. length of token - 1
6161da177e4SLinus Torvalds  *
6171da177e4SLinus Torvalds  * Parses the original string and copy a token to the given
6181da177e4SLinus Torvalds  * string buffer.
6191da177e4SLinus Torvalds  *
620eb7c06e8SYacine Belkadi  * Return: The updated pointer of the original string so that
6211da177e4SLinus Torvalds  * it can be used for the next call.
6221da177e4SLinus Torvalds  */
snd_info_get_str(char * dest,const char * src,int len)6234f7454a9STakashi Iwai const char *snd_info_get_str(char *dest, const char *src, int len)
6241da177e4SLinus Torvalds {
6251da177e4SLinus Torvalds 	int c;
6261da177e4SLinus Torvalds 
6271da177e4SLinus Torvalds 	while (*src == ' ' || *src == '\t')
6281da177e4SLinus Torvalds 		src++;
6291da177e4SLinus Torvalds 	if (*src == '"' || *src == '\'') {
6301da177e4SLinus Torvalds 		c = *src++;
6311da177e4SLinus Torvalds 		while (--len > 0 && *src && *src != c) {
6321da177e4SLinus Torvalds 			*dest++ = *src++;
6331da177e4SLinus Torvalds 		}
6341da177e4SLinus Torvalds 		if (*src == c)
6351da177e4SLinus Torvalds 			src++;
6361da177e4SLinus Torvalds 	} else {
6371da177e4SLinus Torvalds 		while (--len > 0 && *src && *src != ' ' && *src != '\t') {
6381da177e4SLinus Torvalds 			*dest++ = *src++;
6391da177e4SLinus Torvalds 		}
6401da177e4SLinus Torvalds 	}
6411da177e4SLinus Torvalds 	*dest = 0;
6421da177e4SLinus Torvalds 	while (*src == ' ' || *src == '\t')
6431da177e4SLinus Torvalds 		src++;
6441da177e4SLinus Torvalds 	return src;
6451da177e4SLinus Torvalds }
646c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_get_str);
647c0d3fb39STakashi Iwai 
648c309c467STakashi Iwai /*
6491da177e4SLinus Torvalds  * snd_info_create_entry - create an info entry
6501da177e4SLinus Torvalds  * @name: the proc file name
6518e7ccb7bSTakashi Iwai  * @parent: the parent directory
6521da177e4SLinus Torvalds  *
6531da177e4SLinus Torvalds  * Creates an info entry with the given file name and initializes as
6541da177e4SLinus Torvalds  * the default state.
6551da177e4SLinus Torvalds  *
6561da177e4SLinus Torvalds  * Usually called from other functions such as
6571da177e4SLinus Torvalds  * snd_info_create_card_entry().
6581da177e4SLinus Torvalds  *
659eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
6601da177e4SLinus Torvalds  */
6618e7ccb7bSTakashi Iwai static struct snd_info_entry *
snd_info_create_entry(const char * name,struct snd_info_entry * parent,struct module * module)662a858ee66STakashi Iwai snd_info_create_entry(const char *name, struct snd_info_entry *parent,
663a858ee66STakashi Iwai 		      struct module *module)
6641da177e4SLinus Torvalds {
66524c1f931STakashi Iwai 	struct snd_info_entry *entry;
666ca2c0966STakashi Iwai 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
6671da177e4SLinus Torvalds 	if (entry == NULL)
6681da177e4SLinus Torvalds 		return NULL;
669543537bdSPaulo Marques 	entry->name = kstrdup(name, GFP_KERNEL);
6701da177e4SLinus Torvalds 	if (entry->name == NULL) {
6711da177e4SLinus Torvalds 		kfree(entry);
6721da177e4SLinus Torvalds 		return NULL;
6731da177e4SLinus Torvalds 	}
6746a73cf46SJoe Perches 	entry->mode = S_IFREG | 0444;
6751da177e4SLinus Torvalds 	entry->content = SNDRV_INFO_CONTENT_TEXT;
6761a60d4c5SIngo Molnar 	mutex_init(&entry->access);
677746d4a02STakashi Iwai 	INIT_LIST_HEAD(&entry->children);
678746d4a02STakashi Iwai 	INIT_LIST_HEAD(&entry->list);
6798e7ccb7bSTakashi Iwai 	entry->parent = parent;
680a858ee66STakashi Iwai 	entry->module = module;
6818c2f8708STakashi Iwai 	if (parent) {
682*4b72362bSTakashi Iwai 		guard(mutex)(&parent->access);
6838e7ccb7bSTakashi Iwai 		list_add_tail(&entry->list, &parent->children);
6848c2f8708STakashi Iwai 	}
6851da177e4SLinus Torvalds 	return entry;
6861da177e4SLinus Torvalds }
6871da177e4SLinus Torvalds 
6881da177e4SLinus Torvalds /**
6891da177e4SLinus Torvalds  * snd_info_create_module_entry - create an info entry for the given module
6901da177e4SLinus Torvalds  * @module: the module pointer
6911da177e4SLinus Torvalds  * @name: the file name
6921da177e4SLinus Torvalds  * @parent: the parent directory
6931da177e4SLinus Torvalds  *
6941da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given module.
6951da177e4SLinus Torvalds  *
696eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
6971da177e4SLinus Torvalds  */
snd_info_create_module_entry(struct module * module,const char * name,struct snd_info_entry * parent)69824c1f931STakashi Iwai struct snd_info_entry *snd_info_create_module_entry(struct module * module,
6991da177e4SLinus Torvalds 					       const char *name,
70024c1f931STakashi Iwai 					       struct snd_info_entry *parent)
7011da177e4SLinus Torvalds {
7023a554371STakashi Iwai 	if (!parent)
7033a554371STakashi Iwai 		parent = snd_proc_root;
704a858ee66STakashi Iwai 	return snd_info_create_entry(name, parent, module);
7051da177e4SLinus Torvalds }
706c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_create_module_entry);
707c0d3fb39STakashi Iwai 
7081da177e4SLinus Torvalds /**
7091da177e4SLinus Torvalds  * snd_info_create_card_entry - create an info entry for the given card
7101da177e4SLinus Torvalds  * @card: the card instance
7111da177e4SLinus Torvalds  * @name: the file name
7121da177e4SLinus Torvalds  * @parent: the parent directory
7131da177e4SLinus Torvalds  *
7141da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given card.
7151da177e4SLinus Torvalds  *
716eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
7171da177e4SLinus Torvalds  */
snd_info_create_card_entry(struct snd_card * card,const char * name,struct snd_info_entry * parent)71824c1f931STakashi Iwai struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card,
7191da177e4SLinus Torvalds 					     const char *name,
72024c1f931STakashi Iwai 					     struct snd_info_entry * parent)
7211da177e4SLinus Torvalds {
7223a554371STakashi Iwai 	if (!parent)
7233a554371STakashi Iwai 		parent = card->proc_root;
724a858ee66STakashi Iwai 	return snd_info_create_entry(name, parent, card->module);
7251da177e4SLinus Torvalds }
726c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_create_card_entry);
727c0d3fb39STakashi Iwai 
snd_info_clear_entries(struct snd_info_entry * entry)728c7a60651STakashi Iwai static void snd_info_clear_entries(struct snd_info_entry *entry)
729746d4a02STakashi Iwai {
73090a409aaSTakashi Iwai 	struct snd_info_entry *p;
731746d4a02STakashi Iwai 
732746d4a02STakashi Iwai 	if (!entry->p)
733746d4a02STakashi Iwai 		return;
73490a409aaSTakashi Iwai 	list_for_each_entry(p, &entry->children, list)
735c7a60651STakashi Iwai 		snd_info_clear_entries(p);
736746d4a02STakashi Iwai 	entry->p = NULL;
737746d4a02STakashi Iwai }
738746d4a02STakashi Iwai 
7391da177e4SLinus Torvalds /**
7401da177e4SLinus Torvalds  * snd_info_free_entry - release the info entry
7411da177e4SLinus Torvalds  * @entry: the info entry
7421da177e4SLinus Torvalds  *
743c560a679STakashi Iwai  * Releases the info entry.
7441da177e4SLinus Torvalds  */
snd_info_free_entry(struct snd_info_entry * entry)74524c1f931STakashi Iwai void snd_info_free_entry(struct snd_info_entry * entry)
7461da177e4SLinus Torvalds {
747c560a679STakashi Iwai 	struct snd_info_entry *p, *n;
748c560a679STakashi Iwai 
749c560a679STakashi Iwai 	if (!entry)
7501da177e4SLinus Torvalds 		return;
751746d4a02STakashi Iwai 	if (entry->p) {
752c7a60651STakashi Iwai 		proc_remove(entry->p);
753*4b72362bSTakashi Iwai 		guard(mutex)(&info_mutex);
754c7a60651STakashi Iwai 		snd_info_clear_entries(entry);
755746d4a02STakashi Iwai 	}
756c560a679STakashi Iwai 
757c560a679STakashi Iwai 	/* free all children at first */
758c560a679STakashi Iwai 	list_for_each_entry_safe(p, n, &entry->children, list)
759c560a679STakashi Iwai 		snd_info_free_entry(p);
760c560a679STakashi Iwai 
7618c2f8708STakashi Iwai 	p = entry->parent;
7628c2f8708STakashi Iwai 	if (p) {
763*4b72362bSTakashi Iwai 		guard(mutex)(&p->access);
76490a409aaSTakashi Iwai 		list_del(&entry->list);
7658c2f8708STakashi Iwai 	}
7661da177e4SLinus Torvalds 	kfree(entry->name);
7671da177e4SLinus Torvalds 	if (entry->private_free)
7681da177e4SLinus Torvalds 		entry->private_free(entry);
7691da177e4SLinus Torvalds 	kfree(entry);
7701da177e4SLinus Torvalds }
771c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_free_entry);
772c0d3fb39STakashi Iwai 
__snd_info_register(struct snd_info_entry * entry)773348c5ad5STakashi Iwai static int __snd_info_register(struct snd_info_entry *entry)
7741da177e4SLinus Torvalds {
7751da177e4SLinus Torvalds 	struct proc_dir_entry *root, *p = NULL;
7761da177e4SLinus Torvalds 
7777eaa943cSTakashi Iwai 	if (snd_BUG_ON(!entry))
7787eaa943cSTakashi Iwai 		return -ENXIO;
779644dbd64STakashi Iwai 	root = entry->parent == NULL ? snd_proc_root->p : entry->parent->p;
780*4b72362bSTakashi Iwai 	guard(mutex)(&info_mutex);
781348c5ad5STakashi Iwai 	if (entry->p || !root)
782*4b72362bSTakashi Iwai 		return 0;
783aee0c612SAl Viro 	if (S_ISDIR(entry->mode)) {
784aee0c612SAl Viro 		p = proc_mkdir_mode(entry->name, entry->mode, root);
785*4b72362bSTakashi Iwai 		if (!p)
7861da177e4SLinus Torvalds 			return -ENOMEM;
787aee0c612SAl Viro 	} else {
78897a32539SAlexey Dobriyan 		const struct proc_ops *ops;
7894adb7bcbSTakashi Iwai 		if (entry->content == SNDRV_INFO_CONTENT_DATA)
7904adb7bcbSTakashi Iwai 			ops = &snd_info_entry_operations;
7914adb7bcbSTakashi Iwai 		else
7924adb7bcbSTakashi Iwai 			ops = &snd_info_text_entry_ops;
793aee0c612SAl Viro 		p = proc_create_data(entry->name, entry->mode, root,
7944adb7bcbSTakashi Iwai 				     ops, entry);
795*4b72362bSTakashi Iwai 		if (!p)
796aee0c612SAl Viro 			return -ENOMEM;
797271a15eaSDavid Howells 		proc_set_size(p, entry->size);
798aee0c612SAl Viro 	}
7991da177e4SLinus Torvalds 	entry->p = p;
8001da177e4SLinus Torvalds 	return 0;
8011da177e4SLinus Torvalds }
802348c5ad5STakashi Iwai 
803348c5ad5STakashi Iwai /**
804348c5ad5STakashi Iwai  * snd_info_register - register the info entry
805348c5ad5STakashi Iwai  * @entry: the info entry
806348c5ad5STakashi Iwai  *
807348c5ad5STakashi Iwai  * Registers the proc info entry.
808348c5ad5STakashi Iwai  * The all children entries are registered recursively.
809348c5ad5STakashi Iwai  *
810348c5ad5STakashi Iwai  * Return: Zero if successful, or a negative error code on failure.
811348c5ad5STakashi Iwai  */
snd_info_register(struct snd_info_entry * entry)812348c5ad5STakashi Iwai int snd_info_register(struct snd_info_entry *entry)
813348c5ad5STakashi Iwai {
814348c5ad5STakashi Iwai 	struct snd_info_entry *p;
815348c5ad5STakashi Iwai 	int err;
816348c5ad5STakashi Iwai 
817348c5ad5STakashi Iwai 	if (!entry->p) {
818348c5ad5STakashi Iwai 		err = __snd_info_register(entry);
819348c5ad5STakashi Iwai 		if (err < 0)
820348c5ad5STakashi Iwai 			return err;
821348c5ad5STakashi Iwai 	}
822348c5ad5STakashi Iwai 
823348c5ad5STakashi Iwai 	list_for_each_entry(p, &entry->children, list) {
824348c5ad5STakashi Iwai 		err = snd_info_register(p);
825348c5ad5STakashi Iwai 		if (err < 0)
826348c5ad5STakashi Iwai 			return err;
827348c5ad5STakashi Iwai 	}
828348c5ad5STakashi Iwai 
829348c5ad5STakashi Iwai 	return 0;
830348c5ad5STakashi Iwai }
831c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_register);
832c0d3fb39STakashi Iwai 
8337453e1daSTakashi Iwai /**
8347453e1daSTakashi Iwai  * snd_card_rw_proc_new - Create a read/write text proc file entry for the card
8357453e1daSTakashi Iwai  * @card: the card instance
8367453e1daSTakashi Iwai  * @name: the file name
8377453e1daSTakashi Iwai  * @private_data: the arbitrary private data
8387453e1daSTakashi Iwai  * @read: the read callback
8397453e1daSTakashi Iwai  * @write: the write callback, NULL for read-only
8407453e1daSTakashi Iwai  *
8417453e1daSTakashi Iwai  * This proc file entry will be registered via snd_card_register() call, and
8427453e1daSTakashi Iwai  * it will be removed automatically at the card removal, too.
843281dee67STakashi Iwai  *
844281dee67STakashi Iwai  * Return: zero if successful, or a negative error code
8457453e1daSTakashi 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))8467453e1daSTakashi Iwai int snd_card_rw_proc_new(struct snd_card *card, const char *name,
8477453e1daSTakashi Iwai 			 void *private_data,
8487453e1daSTakashi Iwai 			 void (*read)(struct snd_info_entry *,
8497453e1daSTakashi Iwai 				      struct snd_info_buffer *),
8507453e1daSTakashi Iwai 			 void (*write)(struct snd_info_entry *entry,
8517453e1daSTakashi Iwai 				       struct snd_info_buffer *buffer))
8527453e1daSTakashi Iwai {
8537453e1daSTakashi Iwai 	struct snd_info_entry *entry;
8547453e1daSTakashi Iwai 
8557453e1daSTakashi Iwai 	entry = snd_info_create_card_entry(card, name, card->proc_root);
8567453e1daSTakashi Iwai 	if (!entry)
8577453e1daSTakashi Iwai 		return -ENOMEM;
8587453e1daSTakashi Iwai 	snd_info_set_text_ops(entry, private_data, read);
8597453e1daSTakashi Iwai 	if (write) {
8607453e1daSTakashi Iwai 		entry->mode |= 0200;
8617453e1daSTakashi Iwai 		entry->c.text.write = write;
8627453e1daSTakashi Iwai 	}
8637453e1daSTakashi Iwai 	return 0;
8647453e1daSTakashi Iwai }
8657453e1daSTakashi Iwai EXPORT_SYMBOL_GPL(snd_card_rw_proc_new);
8667453e1daSTakashi Iwai 
8671da177e4SLinus Torvalds /*
8681da177e4SLinus Torvalds 
8691da177e4SLinus Torvalds  */
8701da177e4SLinus Torvalds 
snd_info_version_read(struct snd_info_entry * entry,struct snd_info_buffer * buffer)87124c1f931STakashi Iwai static void snd_info_version_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
8721da177e4SLinus Torvalds {
8731da177e4SLinus Torvalds 	snd_iprintf(buffer,
87442662748SJaroslav Kysela 		    "Advanced Linux Sound Architecture Driver Version k%s.\n",
87542662748SJaroslav Kysela 		    init_utsname()->release);
8761da177e4SLinus Torvalds }
8771da177e4SLinus Torvalds 
snd_info_version_init(void)8781da177e4SLinus Torvalds static int __init snd_info_version_init(void)
8791da177e4SLinus Torvalds {
88024c1f931STakashi Iwai 	struct snd_info_entry *entry;
8811da177e4SLinus Torvalds 
8821da177e4SLinus Torvalds 	entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL);
8831da177e4SLinus Torvalds 	if (entry == NULL)
8841da177e4SLinus Torvalds 		return -ENOMEM;
8851da177e4SLinus Torvalds 	entry->c.text.read = snd_info_version_read;
886b591b6e9STakashi Iwai 	return snd_info_register(entry); /* freed in error path */
8871da177e4SLinus Torvalds }
888