forked from Minki/linux
543537bd92
This patch creates a new kstrdup library function and changes the "local" implementations in several places to use this function. Most of the changes come from the sound and net subsystems. The sound part had already been acknowledged by Takashi Iwai and the net part by David S. Miller. I left UML alone for now because I would need more time to read the code carefully before making changes there. Signed-off-by: Paulo Marques <pmarques@grupopie.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
491 lines
13 KiB
C
491 lines
13 KiB
C
/*
|
|
* Advanced Linux Sound Architecture
|
|
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <sound/driver.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/time.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <sound/core.h>
|
|
#include <sound/minors.h>
|
|
#include <sound/info.h>
|
|
#include <sound/version.h>
|
|
#include <sound/control.h>
|
|
#include <sound/initval.h>
|
|
#include <linux/kmod.h>
|
|
#include <linux/devfs_fs_kernel.h>
|
|
#include <linux/device.h>
|
|
|
|
#define SNDRV_OS_MINORS 256
|
|
|
|
static int major = CONFIG_SND_MAJOR;
|
|
int snd_major;
|
|
static int cards_limit = 1;
|
|
static int device_mode = S_IFCHR | S_IRUGO | S_IWUGO;
|
|
|
|
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
|
|
MODULE_DESCRIPTION("Advanced Linux Sound Architecture driver for soundcards.");
|
|
MODULE_LICENSE("GPL");
|
|
module_param(major, int, 0444);
|
|
MODULE_PARM_DESC(major, "Major # for sound driver.");
|
|
module_param(cards_limit, int, 0444);
|
|
MODULE_PARM_DESC(cards_limit, "Count of auto-loadable soundcards.");
|
|
#ifdef CONFIG_DEVFS_FS
|
|
module_param(device_mode, int, 0444);
|
|
MODULE_PARM_DESC(device_mode, "Device file permission mask for devfs.");
|
|
#endif
|
|
MODULE_ALIAS_CHARDEV_MAJOR(CONFIG_SND_MAJOR);
|
|
|
|
/* this one holds the actual max. card number currently available.
|
|
* as default, it's identical with cards_limit option. when more
|
|
* modules are loaded manually, this limit number increases, too.
|
|
*/
|
|
int snd_ecards_limit;
|
|
|
|
static struct list_head snd_minors_hash[SNDRV_CARDS];
|
|
|
|
static DECLARE_MUTEX(sound_mutex);
|
|
|
|
extern struct class *sound_class;
|
|
|
|
|
|
#ifdef CONFIG_KMOD
|
|
|
|
/**
|
|
* snd_request_card - try to load the card module
|
|
* @card: the card number
|
|
*
|
|
* Tries to load the module "snd-card-X" for the given card number
|
|
* via KMOD. Returns immediately if already loaded.
|
|
*/
|
|
void snd_request_card(int card)
|
|
{
|
|
int locked;
|
|
|
|
if (! current->fs->root)
|
|
return;
|
|
read_lock(&snd_card_rwlock);
|
|
locked = snd_cards_lock & (1 << card);
|
|
read_unlock(&snd_card_rwlock);
|
|
if (locked)
|
|
return;
|
|
if (card < 0 || card >= cards_limit)
|
|
return;
|
|
request_module("snd-card-%i", card);
|
|
}
|
|
|
|
static void snd_request_other(int minor)
|
|
{
|
|
char *str;
|
|
|
|
if (! current->fs->root)
|
|
return;
|
|
switch (minor) {
|
|
case SNDRV_MINOR_SEQUENCER: str = "snd-seq"; break;
|
|
case SNDRV_MINOR_TIMER: str = "snd-timer"; break;
|
|
default: return;
|
|
}
|
|
request_module(str);
|
|
}
|
|
|
|
#endif /* request_module support */
|
|
|
|
static snd_minor_t *snd_minor_search(int minor)
|
|
{
|
|
struct list_head *list;
|
|
snd_minor_t *mptr;
|
|
|
|
list_for_each(list, &snd_minors_hash[SNDRV_MINOR_CARD(minor)]) {
|
|
mptr = list_entry(list, snd_minor_t, list);
|
|
if (mptr->number == minor)
|
|
return mptr;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int snd_open(struct inode *inode, struct file *file)
|
|
{
|
|
int minor = iminor(inode);
|
|
int card = SNDRV_MINOR_CARD(minor);
|
|
int dev = SNDRV_MINOR_DEVICE(minor);
|
|
snd_minor_t *mptr = NULL;
|
|
struct file_operations *old_fops;
|
|
int err = 0;
|
|
|
|
if (dev != SNDRV_MINOR_SEQUENCER && dev != SNDRV_MINOR_TIMER) {
|
|
if (snd_cards[card] == NULL) {
|
|
#ifdef CONFIG_KMOD
|
|
snd_request_card(card);
|
|
if (snd_cards[card] == NULL)
|
|
#endif
|
|
return -ENODEV;
|
|
}
|
|
} else {
|
|
#ifdef CONFIG_KMOD
|
|
if ((mptr = snd_minor_search(minor)) == NULL)
|
|
snd_request_other(minor);
|
|
#endif
|
|
}
|
|
if (mptr == NULL && (mptr = snd_minor_search(minor)) == NULL)
|
|
return -ENODEV;
|
|
old_fops = file->f_op;
|
|
file->f_op = fops_get(mptr->f_ops);
|
|
if (file->f_op->open)
|
|
err = file->f_op->open(inode, file);
|
|
if (err) {
|
|
fops_put(file->f_op);
|
|
file->f_op = fops_get(old_fops);
|
|
}
|
|
fops_put(old_fops);
|
|
return err;
|
|
}
|
|
|
|
static struct file_operations snd_fops =
|
|
{
|
|
.owner = THIS_MODULE,
|
|
.open = snd_open
|
|
};
|
|
|
|
static int snd_kernel_minor(int type, snd_card_t * card, int dev)
|
|
{
|
|
int minor;
|
|
|
|
switch (type) {
|
|
case SNDRV_DEVICE_TYPE_SEQUENCER:
|
|
case SNDRV_DEVICE_TYPE_TIMER:
|
|
minor = type;
|
|
break;
|
|
case SNDRV_DEVICE_TYPE_CONTROL:
|
|
snd_assert(card != NULL, return -EINVAL);
|
|
minor = SNDRV_MINOR(card->number, type);
|
|
break;
|
|
case SNDRV_DEVICE_TYPE_HWDEP:
|
|
case SNDRV_DEVICE_TYPE_RAWMIDI:
|
|
case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
|
|
case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
|
|
snd_assert(card != NULL, return -EINVAL);
|
|
minor = SNDRV_MINOR(card->number, type + dev);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
snd_assert(minor >= 0 && minor < SNDRV_OS_MINORS, return -EINVAL);
|
|
return minor;
|
|
}
|
|
|
|
/**
|
|
* snd_register_device - Register the ALSA device file for the card
|
|
* @type: the device type, SNDRV_DEVICE_TYPE_XXX
|
|
* @card: the card instance
|
|
* @dev: the device index
|
|
* @reg: the snd_minor_t record
|
|
* @name: the device file name
|
|
*
|
|
* Registers an ALSA device file for the given card.
|
|
* The operators have to be set in reg parameter.
|
|
*
|
|
* Retrurns zero if successful, or a negative error code on failure.
|
|
*/
|
|
int snd_register_device(int type, snd_card_t * card, int dev, snd_minor_t * reg, const char *name)
|
|
{
|
|
int minor = snd_kernel_minor(type, card, dev);
|
|
snd_minor_t *preg;
|
|
struct device *device = NULL;
|
|
|
|
if (minor < 0)
|
|
return minor;
|
|
snd_assert(name, return -EINVAL);
|
|
preg = (snd_minor_t *)kmalloc(sizeof(snd_minor_t) + strlen(name) + 1, GFP_KERNEL);
|
|
if (preg == NULL)
|
|
return -ENOMEM;
|
|
*preg = *reg;
|
|
preg->number = minor;
|
|
preg->device = dev;
|
|
strcpy(preg->name, name);
|
|
down(&sound_mutex);
|
|
if (snd_minor_search(minor)) {
|
|
up(&sound_mutex);
|
|
kfree(preg);
|
|
return -EBUSY;
|
|
}
|
|
list_add_tail(&preg->list, &snd_minors_hash[SNDRV_MINOR_CARD(minor)]);
|
|
if (strncmp(name, "controlC", 8) || card->number >= cards_limit)
|
|
devfs_mk_cdev(MKDEV(major, minor), S_IFCHR | device_mode, "snd/%s", name);
|
|
if (card)
|
|
device = card->dev;
|
|
class_device_create(sound_class, MKDEV(major, minor), device, "%s", name);
|
|
|
|
up(&sound_mutex);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* snd_unregister_device - unregister the device on the given card
|
|
* @type: the device type, SNDRV_DEVICE_TYPE_XXX
|
|
* @card: the card instance
|
|
* @dev: the device index
|
|
*
|
|
* Unregisters the device file already registered via
|
|
* snd_register_device().
|
|
*
|
|
* Returns zero if sucecessful, or a negative error code on failure
|
|
*/
|
|
int snd_unregister_device(int type, snd_card_t * card, int dev)
|
|
{
|
|
int minor = snd_kernel_minor(type, card, dev);
|
|
snd_minor_t *mptr;
|
|
|
|
if (minor < 0)
|
|
return minor;
|
|
down(&sound_mutex);
|
|
if ((mptr = snd_minor_search(minor)) == NULL) {
|
|
up(&sound_mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (strncmp(mptr->name, "controlC", 8) || card->number >= cards_limit) /* created in sound.c */
|
|
devfs_remove("snd/%s", mptr->name);
|
|
class_device_destroy(sound_class, MKDEV(major, minor));
|
|
|
|
list_del(&mptr->list);
|
|
up(&sound_mutex);
|
|
kfree(mptr);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* INFO PART
|
|
*/
|
|
|
|
static snd_info_entry_t *snd_minor_info_entry = NULL;
|
|
|
|
static void snd_minor_info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
|
|
{
|
|
int card, device;
|
|
struct list_head *list;
|
|
snd_minor_t *mptr;
|
|
|
|
down(&sound_mutex);
|
|
for (card = 0; card < SNDRV_CARDS; card++) {
|
|
list_for_each(list, &snd_minors_hash[card]) {
|
|
mptr = list_entry(list, snd_minor_t, list);
|
|
if (SNDRV_MINOR_DEVICE(mptr->number) != SNDRV_MINOR_SEQUENCER) {
|
|
if ((device = mptr->device) >= 0)
|
|
snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", mptr->number, card, device, mptr->comment);
|
|
else
|
|
snd_iprintf(buffer, "%3i: [%i] : %s\n", mptr->number, card, mptr->comment);
|
|
} else {
|
|
snd_iprintf(buffer, "%3i: : %s\n", mptr->number, mptr->comment);
|
|
}
|
|
}
|
|
}
|
|
up(&sound_mutex);
|
|
}
|
|
|
|
int __init snd_minor_info_init(void)
|
|
{
|
|
snd_info_entry_t *entry;
|
|
|
|
entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL);
|
|
if (entry) {
|
|
entry->c.text.read_size = PAGE_SIZE;
|
|
entry->c.text.read = snd_minor_info_read;
|
|
if (snd_info_register(entry) < 0) {
|
|
snd_info_free_entry(entry);
|
|
entry = NULL;
|
|
}
|
|
}
|
|
snd_minor_info_entry = entry;
|
|
return 0;
|
|
}
|
|
|
|
int __exit snd_minor_info_done(void)
|
|
{
|
|
if (snd_minor_info_entry)
|
|
snd_info_unregister(snd_minor_info_entry);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* INIT PART
|
|
*/
|
|
|
|
static int __init alsa_sound_init(void)
|
|
{
|
|
short controlnum;
|
|
int err;
|
|
int card;
|
|
|
|
snd_major = major;
|
|
snd_ecards_limit = cards_limit;
|
|
for (card = 0; card < SNDRV_CARDS; card++)
|
|
INIT_LIST_HEAD(&snd_minors_hash[card]);
|
|
if ((err = snd_oss_init_module()) < 0)
|
|
return err;
|
|
devfs_mk_dir("snd");
|
|
if (register_chrdev(major, "alsa", &snd_fops)) {
|
|
snd_printk(KERN_ERR "unable to register native major device number %d\n", major);
|
|
devfs_remove("snd");
|
|
return -EIO;
|
|
}
|
|
snd_memory_init();
|
|
if (snd_info_init() < 0) {
|
|
snd_memory_done();
|
|
unregister_chrdev(major, "alsa");
|
|
devfs_remove("snd");
|
|
return -ENOMEM;
|
|
}
|
|
snd_info_minor_register();
|
|
for (controlnum = 0; controlnum < cards_limit; controlnum++)
|
|
devfs_mk_cdev(MKDEV(major, controlnum<<5), S_IFCHR | device_mode, "snd/controlC%d", controlnum);
|
|
#ifndef MODULE
|
|
printk(KERN_INFO "Advanced Linux Sound Architecture Driver Version " CONFIG_SND_VERSION CONFIG_SND_DATE ".\n");
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void __exit alsa_sound_exit(void)
|
|
{
|
|
short controlnum;
|
|
|
|
for (controlnum = 0; controlnum < cards_limit; controlnum++)
|
|
devfs_remove("snd/controlC%d", controlnum);
|
|
|
|
snd_info_minor_unregister();
|
|
snd_info_done();
|
|
snd_memory_done();
|
|
if (unregister_chrdev(major, "alsa") != 0)
|
|
snd_printk(KERN_ERR "unable to unregister major device number %d\n", major);
|
|
devfs_remove("snd");
|
|
}
|
|
|
|
module_init(alsa_sound_init)
|
|
module_exit(alsa_sound_exit)
|
|
|
|
/* sound.c */
|
|
EXPORT_SYMBOL(snd_major);
|
|
EXPORT_SYMBOL(snd_ecards_limit);
|
|
#if defined(CONFIG_KMOD)
|
|
EXPORT_SYMBOL(snd_request_card);
|
|
#endif
|
|
EXPORT_SYMBOL(snd_register_device);
|
|
EXPORT_SYMBOL(snd_unregister_device);
|
|
#if defined(CONFIG_SND_OSSEMUL)
|
|
EXPORT_SYMBOL(snd_register_oss_device);
|
|
EXPORT_SYMBOL(snd_unregister_oss_device);
|
|
#endif
|
|
/* memory.c */
|
|
#ifdef CONFIG_SND_DEBUG_MEMORY
|
|
EXPORT_SYMBOL(snd_hidden_kmalloc);
|
|
EXPORT_SYMBOL(snd_hidden_kcalloc);
|
|
EXPORT_SYMBOL(snd_hidden_kfree);
|
|
EXPORT_SYMBOL(snd_hidden_vmalloc);
|
|
EXPORT_SYMBOL(snd_hidden_vfree);
|
|
EXPORT_SYMBOL(snd_hidden_kstrdup);
|
|
#endif
|
|
EXPORT_SYMBOL(copy_to_user_fromio);
|
|
EXPORT_SYMBOL(copy_from_user_toio);
|
|
/* init.c */
|
|
EXPORT_SYMBOL(snd_cards);
|
|
#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
|
|
EXPORT_SYMBOL(snd_mixer_oss_notify_callback);
|
|
#endif
|
|
EXPORT_SYMBOL(snd_card_new);
|
|
EXPORT_SYMBOL(snd_card_disconnect);
|
|
EXPORT_SYMBOL(snd_card_free);
|
|
EXPORT_SYMBOL(snd_card_free_in_thread);
|
|
EXPORT_SYMBOL(snd_card_register);
|
|
EXPORT_SYMBOL(snd_component_add);
|
|
EXPORT_SYMBOL(snd_card_file_add);
|
|
EXPORT_SYMBOL(snd_card_file_remove);
|
|
#ifdef CONFIG_PM
|
|
EXPORT_SYMBOL(snd_power_wait);
|
|
EXPORT_SYMBOL(snd_card_set_pm_callback);
|
|
#if defined(CONFIG_PM) && defined(CONFIG_SND_GENERIC_PM)
|
|
EXPORT_SYMBOL(snd_card_set_generic_pm_callback);
|
|
#endif
|
|
#ifdef CONFIG_PCI
|
|
EXPORT_SYMBOL(snd_card_pci_suspend);
|
|
EXPORT_SYMBOL(snd_card_pci_resume);
|
|
#endif
|
|
#endif
|
|
/* device.c */
|
|
EXPORT_SYMBOL(snd_device_new);
|
|
EXPORT_SYMBOL(snd_device_register);
|
|
EXPORT_SYMBOL(snd_device_free);
|
|
/* isadma.c */
|
|
#ifdef CONFIG_ISA
|
|
EXPORT_SYMBOL(snd_dma_program);
|
|
EXPORT_SYMBOL(snd_dma_disable);
|
|
EXPORT_SYMBOL(snd_dma_pointer);
|
|
#endif
|
|
/* info.c */
|
|
#ifdef CONFIG_PROC_FS
|
|
EXPORT_SYMBOL(snd_seq_root);
|
|
EXPORT_SYMBOL(snd_iprintf);
|
|
EXPORT_SYMBOL(snd_info_get_line);
|
|
EXPORT_SYMBOL(snd_info_get_str);
|
|
EXPORT_SYMBOL(snd_info_create_module_entry);
|
|
EXPORT_SYMBOL(snd_info_create_card_entry);
|
|
EXPORT_SYMBOL(snd_info_free_entry);
|
|
EXPORT_SYMBOL(snd_info_register);
|
|
EXPORT_SYMBOL(snd_info_unregister);
|
|
EXPORT_SYMBOL(snd_card_proc_new);
|
|
#endif
|
|
/* info_oss.c */
|
|
#if defined(CONFIG_SND_OSSEMUL) && defined(CONFIG_PROC_FS)
|
|
EXPORT_SYMBOL(snd_oss_info_register);
|
|
#endif
|
|
/* control.c */
|
|
EXPORT_SYMBOL(snd_ctl_new);
|
|
EXPORT_SYMBOL(snd_ctl_new1);
|
|
EXPORT_SYMBOL(snd_ctl_free_one);
|
|
EXPORT_SYMBOL(snd_ctl_add);
|
|
EXPORT_SYMBOL(snd_ctl_remove);
|
|
EXPORT_SYMBOL(snd_ctl_remove_id);
|
|
EXPORT_SYMBOL(snd_ctl_rename_id);
|
|
EXPORT_SYMBOL(snd_ctl_find_numid);
|
|
EXPORT_SYMBOL(snd_ctl_find_id);
|
|
EXPORT_SYMBOL(snd_ctl_notify);
|
|
EXPORT_SYMBOL(snd_ctl_register_ioctl);
|
|
EXPORT_SYMBOL(snd_ctl_unregister_ioctl);
|
|
#ifdef CONFIG_COMPAT
|
|
EXPORT_SYMBOL(snd_ctl_register_ioctl_compat);
|
|
EXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat);
|
|
#endif
|
|
EXPORT_SYMBOL(snd_ctl_elem_read);
|
|
EXPORT_SYMBOL(snd_ctl_elem_write);
|
|
/* misc.c */
|
|
EXPORT_SYMBOL(snd_task_name);
|
|
#ifdef CONFIG_SND_VERBOSE_PRINTK
|
|
EXPORT_SYMBOL(snd_verbose_printk);
|
|
#endif
|
|
#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_VERBOSE_PRINTK)
|
|
EXPORT_SYMBOL(snd_verbose_printd);
|
|
#endif
|
|
/* wrappers */
|
|
#ifdef CONFIG_SND_DEBUG_MEMORY
|
|
EXPORT_SYMBOL(snd_wrapper_kmalloc);
|
|
EXPORT_SYMBOL(snd_wrapper_kfree);
|
|
EXPORT_SYMBOL(snd_wrapper_vmalloc);
|
|
EXPORT_SYMBOL(snd_wrapper_vfree);
|
|
#endif
|