|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | *  OSS emulation layer for the mixer interface | 
|  | *  Copyright (c) by Jaroslav Kysela <perex@perex.cz> | 
|  | */ | 
|  |  | 
|  | #include <linux/init.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/time.h> | 
|  | #include <linux/string.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/compat.h> | 
|  | #include <sound/core.h> | 
|  | #include <sound/minors.h> | 
|  | #include <sound/control.h> | 
|  | #include <sound/info.h> | 
|  | #include <sound/mixer_oss.h> | 
|  | #include <linux/soundcard.h> | 
|  |  | 
|  | #define OSS_ALSAEMULVER         _SIOR ('M', 249, int) | 
|  |  | 
|  | MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); | 
|  | MODULE_DESCRIPTION("Mixer OSS emulation for ALSA."); | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MIXER); | 
|  |  | 
|  | static int snd_mixer_oss_open(struct inode *inode, struct file *file) | 
|  | { | 
|  | struct snd_card *card; | 
|  | struct snd_mixer_oss_file *fmixer; | 
|  | int err; | 
|  |  | 
|  | err = nonseekable_open(inode, file); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | card = snd_lookup_oss_minor_data(iminor(inode), | 
|  | SNDRV_OSS_DEVICE_TYPE_MIXER); | 
|  | if (card == NULL) | 
|  | return -ENODEV; | 
|  | if (card->mixer_oss == NULL) { | 
|  | snd_card_unref(card); | 
|  | return -ENODEV; | 
|  | } | 
|  | err = snd_card_file_add(card, file); | 
|  | if (err < 0) { | 
|  | snd_card_unref(card); | 
|  | return err; | 
|  | } | 
|  | fmixer = kzalloc(sizeof(*fmixer), GFP_KERNEL); | 
|  | if (fmixer == NULL) { | 
|  | snd_card_file_remove(card, file); | 
|  | snd_card_unref(card); | 
|  | return -ENOMEM; | 
|  | } | 
|  | fmixer->card = card; | 
|  | fmixer->mixer = card->mixer_oss; | 
|  | file->private_data = fmixer; | 
|  | if (!try_module_get(card->module)) { | 
|  | kfree(fmixer); | 
|  | snd_card_file_remove(card, file); | 
|  | snd_card_unref(card); | 
|  | return -EFAULT; | 
|  | } | 
|  | snd_card_unref(card); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_release(struct inode *inode, struct file *file) | 
|  | { | 
|  | struct snd_mixer_oss_file *fmixer; | 
|  |  | 
|  | if (file->private_data) { | 
|  | fmixer = file->private_data; | 
|  | module_put(fmixer->card->module); | 
|  | snd_card_file_remove(fmixer->card, file); | 
|  | kfree(fmixer); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_info(struct snd_mixer_oss_file *fmixer, | 
|  | mixer_info __user *_info) | 
|  | { | 
|  | struct snd_card *card = fmixer->card; | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | struct mixer_info info; | 
|  |  | 
|  | memset(&info, 0, sizeof(info)); | 
|  | strscpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id)); | 
|  | strscpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name)); | 
|  | info.modify_counter = card->mixer_oss_change_count; | 
|  | if (copy_to_user(_info, &info, sizeof(info))) | 
|  | return -EFAULT; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_info_obsolete(struct snd_mixer_oss_file *fmixer, | 
|  | _old_mixer_info __user *_info) | 
|  | { | 
|  | struct snd_card *card = fmixer->card; | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | _old_mixer_info info; | 
|  |  | 
|  | memset(&info, 0, sizeof(info)); | 
|  | strscpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id)); | 
|  | strscpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name)); | 
|  | if (copy_to_user(_info, &info, sizeof(info))) | 
|  | return -EFAULT; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_caps(struct snd_mixer_oss_file *fmixer) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | int result = 0; | 
|  |  | 
|  | if (mixer == NULL) | 
|  | return -EIO; | 
|  | if (mixer->get_recsrc && mixer->put_recsrc) | 
|  | result |= SOUND_CAP_EXCL_INPUT; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_devmask(struct snd_mixer_oss_file *fmixer) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | struct snd_mixer_oss_slot *pslot; | 
|  | int result = 0, chn; | 
|  |  | 
|  | if (mixer == NULL) | 
|  | return -EIO; | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | for (chn = 0; chn < 31; chn++) { | 
|  | pslot = &mixer->slots[chn]; | 
|  | if (pslot->put_volume || pslot->put_recsrc) | 
|  | result |= 1 << chn; | 
|  | } | 
|  | mutex_unlock(&mixer->reg_mutex); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_stereodevs(struct snd_mixer_oss_file *fmixer) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | struct snd_mixer_oss_slot *pslot; | 
|  | int result = 0, chn; | 
|  |  | 
|  | if (mixer == NULL) | 
|  | return -EIO; | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | for (chn = 0; chn < 31; chn++) { | 
|  | pslot = &mixer->slots[chn]; | 
|  | if (pslot->put_volume && pslot->stereo) | 
|  | result |= 1 << chn; | 
|  | } | 
|  | mutex_unlock(&mixer->reg_mutex); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_recmask(struct snd_mixer_oss_file *fmixer) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | int result = 0; | 
|  |  | 
|  | if (mixer == NULL) | 
|  | return -EIO; | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | if (mixer->put_recsrc && mixer->get_recsrc) {	/* exclusive */ | 
|  | result = mixer->mask_recsrc; | 
|  | } else { | 
|  | struct snd_mixer_oss_slot *pslot; | 
|  | int chn; | 
|  | for (chn = 0; chn < 31; chn++) { | 
|  | pslot = &mixer->slots[chn]; | 
|  | if (pslot->put_recsrc) | 
|  | result |= 1 << chn; | 
|  | } | 
|  | } | 
|  | mutex_unlock(&mixer->reg_mutex); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_get_recsrc(struct snd_mixer_oss_file *fmixer) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | int result = 0; | 
|  |  | 
|  | if (mixer == NULL) | 
|  | return -EIO; | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | if (mixer->put_recsrc && mixer->get_recsrc) {	/* exclusive */ | 
|  | unsigned int index; | 
|  | result = mixer->get_recsrc(fmixer, &index); | 
|  | if (result < 0) | 
|  | goto unlock; | 
|  | result = 1 << index; | 
|  | } else { | 
|  | struct snd_mixer_oss_slot *pslot; | 
|  | int chn; | 
|  | for (chn = 0; chn < 31; chn++) { | 
|  | pslot = &mixer->slots[chn]; | 
|  | if (pslot->get_recsrc) { | 
|  | int active = 0; | 
|  | pslot->get_recsrc(fmixer, pslot, &active); | 
|  | if (active) | 
|  | result |= 1 << chn; | 
|  | } | 
|  | } | 
|  | } | 
|  | mixer->oss_recsrc = result; | 
|  | unlock: | 
|  | mutex_unlock(&mixer->reg_mutex); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_set_recsrc(struct snd_mixer_oss_file *fmixer, int recsrc) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | struct snd_mixer_oss_slot *pslot; | 
|  | int chn, active; | 
|  | unsigned int index; | 
|  | int result = 0; | 
|  |  | 
|  | if (mixer == NULL) | 
|  | return -EIO; | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | if (mixer->get_recsrc && mixer->put_recsrc) {	/* exclusive input */ | 
|  | if (recsrc & ~mixer->oss_recsrc) | 
|  | recsrc &= ~mixer->oss_recsrc; | 
|  | mixer->put_recsrc(fmixer, ffz(~recsrc)); | 
|  | mixer->get_recsrc(fmixer, &index); | 
|  | result = 1 << index; | 
|  | } | 
|  | for (chn = 0; chn < 31; chn++) { | 
|  | pslot = &mixer->slots[chn]; | 
|  | if (pslot->put_recsrc) { | 
|  | active = (recsrc & (1 << chn)) ? 1 : 0; | 
|  | pslot->put_recsrc(fmixer, pslot, active); | 
|  | } | 
|  | } | 
|  | if (! result) { | 
|  | for (chn = 0; chn < 31; chn++) { | 
|  | pslot = &mixer->slots[chn]; | 
|  | if (pslot->get_recsrc) { | 
|  | active = 0; | 
|  | pslot->get_recsrc(fmixer, pslot, &active); | 
|  | if (active) | 
|  | result |= 1 << chn; | 
|  | } | 
|  | } | 
|  | } | 
|  | mutex_unlock(&mixer->reg_mutex); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_get_volume(struct snd_mixer_oss_file *fmixer, int slot) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | struct snd_mixer_oss_slot *pslot; | 
|  | int result = 0, left, right; | 
|  |  | 
|  | if (mixer == NULL || slot > 30) | 
|  | return -EIO; | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | pslot = &mixer->slots[slot]; | 
|  | left = pslot->volume[0]; | 
|  | right = pslot->volume[1]; | 
|  | if (pslot->get_volume) | 
|  | result = pslot->get_volume(fmixer, pslot, &left, &right); | 
|  | if (!pslot->stereo) | 
|  | right = left; | 
|  | if (snd_BUG_ON(left < 0 || left > 100)) { | 
|  | result = -EIO; | 
|  | goto unlock; | 
|  | } | 
|  | if (snd_BUG_ON(right < 0 || right > 100)) { | 
|  | result = -EIO; | 
|  | goto unlock; | 
|  | } | 
|  | if (result >= 0) { | 
|  | pslot->volume[0] = left; | 
|  | pslot->volume[1] = right; | 
|  | result = (left & 0xff) | ((right & 0xff) << 8); | 
|  | } | 
|  | unlock: | 
|  | mutex_unlock(&mixer->reg_mutex); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_set_volume(struct snd_mixer_oss_file *fmixer, | 
|  | int slot, int volume) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | struct snd_mixer_oss_slot *pslot; | 
|  | int result = 0, left = volume & 0xff, right = (volume >> 8) & 0xff; | 
|  |  | 
|  | if (mixer == NULL || slot > 30) | 
|  | return -EIO; | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | pslot = &mixer->slots[slot]; | 
|  | if (left > 100) | 
|  | left = 100; | 
|  | if (right > 100) | 
|  | right = 100; | 
|  | if (!pslot->stereo) | 
|  | right = left; | 
|  | if (pslot->put_volume) | 
|  | result = pslot->put_volume(fmixer, pslot, left, right); | 
|  | if (result < 0) | 
|  | goto unlock; | 
|  | pslot->volume[0] = left; | 
|  | pslot->volume[1] = right; | 
|  | result = (left & 0xff) | ((right & 0xff) << 8); | 
|  | unlock: | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_ioctl1(struct snd_mixer_oss_file *fmixer, unsigned int cmd, unsigned long arg) | 
|  | { | 
|  | void __user *argp = (void __user *)arg; | 
|  | int __user *p = argp; | 
|  | int tmp; | 
|  |  | 
|  | if (snd_BUG_ON(!fmixer)) | 
|  | return -ENXIO; | 
|  | if (((cmd >> 8) & 0xff) == 'M') { | 
|  | switch (cmd) { | 
|  | case SOUND_MIXER_INFO: | 
|  | return snd_mixer_oss_info(fmixer, argp); | 
|  | case SOUND_OLD_MIXER_INFO: | 
|  | return snd_mixer_oss_info_obsolete(fmixer, argp); | 
|  | case SOUND_MIXER_WRITE_RECSRC: | 
|  | if (get_user(tmp, p)) | 
|  | return -EFAULT; | 
|  | tmp = snd_mixer_oss_set_recsrc(fmixer, tmp); | 
|  | if (tmp < 0) | 
|  | return tmp; | 
|  | return put_user(tmp, p); | 
|  | case OSS_GETVERSION: | 
|  | return put_user(SNDRV_OSS_VERSION, p); | 
|  | case OSS_ALSAEMULVER: | 
|  | return put_user(1, p); | 
|  | case SOUND_MIXER_READ_DEVMASK: | 
|  | tmp = snd_mixer_oss_devmask(fmixer); | 
|  | if (tmp < 0) | 
|  | return tmp; | 
|  | return put_user(tmp, p); | 
|  | case SOUND_MIXER_READ_STEREODEVS: | 
|  | tmp = snd_mixer_oss_stereodevs(fmixer); | 
|  | if (tmp < 0) | 
|  | return tmp; | 
|  | return put_user(tmp, p); | 
|  | case SOUND_MIXER_READ_RECMASK: | 
|  | tmp = snd_mixer_oss_recmask(fmixer); | 
|  | if (tmp < 0) | 
|  | return tmp; | 
|  | return put_user(tmp, p); | 
|  | case SOUND_MIXER_READ_CAPS: | 
|  | tmp = snd_mixer_oss_caps(fmixer); | 
|  | if (tmp < 0) | 
|  | return tmp; | 
|  | return put_user(tmp, p); | 
|  | case SOUND_MIXER_READ_RECSRC: | 
|  | tmp = snd_mixer_oss_get_recsrc(fmixer); | 
|  | if (tmp < 0) | 
|  | return tmp; | 
|  | return put_user(tmp, p); | 
|  | } | 
|  | } | 
|  | if (cmd & SIOC_IN) { | 
|  | if (get_user(tmp, p)) | 
|  | return -EFAULT; | 
|  | tmp = snd_mixer_oss_set_volume(fmixer, cmd & 0xff, tmp); | 
|  | if (tmp < 0) | 
|  | return tmp; | 
|  | return put_user(tmp, p); | 
|  | } else if (cmd & SIOC_OUT) { | 
|  | tmp = snd_mixer_oss_get_volume(fmixer, cmd & 0xff); | 
|  | if (tmp < 0) | 
|  | return tmp; | 
|  | return put_user(tmp, p); | 
|  | } | 
|  | return -ENXIO; | 
|  | } | 
|  |  | 
|  | static long snd_mixer_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | 
|  | { | 
|  | return snd_mixer_oss_ioctl1(file->private_data, cmd, arg); | 
|  | } | 
|  |  | 
|  | int snd_mixer_oss_ioctl_card(struct snd_card *card, unsigned int cmd, unsigned long arg) | 
|  | { | 
|  | struct snd_mixer_oss_file fmixer; | 
|  |  | 
|  | if (snd_BUG_ON(!card)) | 
|  | return -ENXIO; | 
|  | if (card->mixer_oss == NULL) | 
|  | return -ENXIO; | 
|  | memset(&fmixer, 0, sizeof(fmixer)); | 
|  | fmixer.card = card; | 
|  | fmixer.mixer = card->mixer_oss; | 
|  | return snd_mixer_oss_ioctl1(&fmixer, cmd, arg); | 
|  | } | 
|  | EXPORT_SYMBOL(snd_mixer_oss_ioctl_card); | 
|  |  | 
|  | #ifdef CONFIG_COMPAT | 
|  | /* all compatible */ | 
|  | static long snd_mixer_oss_ioctl_compat(struct file *file, unsigned int cmd, | 
|  | unsigned long arg) | 
|  | { | 
|  | return snd_mixer_oss_ioctl1(file->private_data, cmd, | 
|  | (unsigned long)compat_ptr(arg)); | 
|  | } | 
|  | #else | 
|  | #define snd_mixer_oss_ioctl_compat	NULL | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | *  REGISTRATION PART | 
|  | */ | 
|  |  | 
|  | static const struct file_operations snd_mixer_oss_f_ops = | 
|  | { | 
|  | .owner =	THIS_MODULE, | 
|  | .open =		snd_mixer_oss_open, | 
|  | .release =	snd_mixer_oss_release, | 
|  | .llseek =	no_llseek, | 
|  | .unlocked_ioctl =	snd_mixer_oss_ioctl, | 
|  | .compat_ioctl =	snd_mixer_oss_ioctl_compat, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | *  utilities | 
|  | */ | 
|  |  | 
|  | static long snd_mixer_oss_conv(long val, long omin, long omax, long nmin, long nmax) | 
|  | { | 
|  | long orange = omax - omin, nrange = nmax - nmin; | 
|  |  | 
|  | if (orange == 0) | 
|  | return 0; | 
|  | return DIV_ROUND_CLOSEST(nrange * (val - omin), orange) + nmin; | 
|  | } | 
|  |  | 
|  | /* convert from alsa native to oss values (0-100) */ | 
|  | static long snd_mixer_oss_conv1(long val, long min, long max, int *old) | 
|  | { | 
|  | if (val == snd_mixer_oss_conv(*old, 0, 100, min, max)) | 
|  | return *old; | 
|  | return snd_mixer_oss_conv(val, min, max, 0, 100); | 
|  | } | 
|  |  | 
|  | /* convert from oss to alsa native values */ | 
|  | static long snd_mixer_oss_conv2(long val, long min, long max) | 
|  | { | 
|  | return snd_mixer_oss_conv(val, 0, 100, min, max); | 
|  | } | 
|  |  | 
|  | #if 0 | 
|  | static void snd_mixer_oss_recsrce_set(struct snd_card *card, int slot) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = card->mixer_oss; | 
|  | if (mixer) | 
|  | mixer->mask_recsrc |= 1 << slot; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_recsrce_get(struct snd_card *card, int slot) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = card->mixer_oss; | 
|  | if (mixer && (mixer->mask_recsrc & (1 << slot))) | 
|  | return 1; | 
|  | return 0; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | #define SNDRV_MIXER_OSS_SIGNATURE		0x65999250 | 
|  |  | 
|  | #define SNDRV_MIXER_OSS_ITEM_GLOBAL	0 | 
|  | #define SNDRV_MIXER_OSS_ITEM_GSWITCH	1 | 
|  | #define SNDRV_MIXER_OSS_ITEM_GROUTE	2 | 
|  | #define SNDRV_MIXER_OSS_ITEM_GVOLUME	3 | 
|  | #define SNDRV_MIXER_OSS_ITEM_PSWITCH	4 | 
|  | #define SNDRV_MIXER_OSS_ITEM_PROUTE	5 | 
|  | #define SNDRV_MIXER_OSS_ITEM_PVOLUME	6 | 
|  | #define SNDRV_MIXER_OSS_ITEM_CSWITCH	7 | 
|  | #define SNDRV_MIXER_OSS_ITEM_CROUTE	8 | 
|  | #define SNDRV_MIXER_OSS_ITEM_CVOLUME	9 | 
|  | #define SNDRV_MIXER_OSS_ITEM_CAPTURE	10 | 
|  |  | 
|  | #define SNDRV_MIXER_OSS_ITEM_COUNT	11 | 
|  |  | 
|  | #define SNDRV_MIXER_OSS_PRESENT_GLOBAL	(1<<0) | 
|  | #define SNDRV_MIXER_OSS_PRESENT_GSWITCH	(1<<1) | 
|  | #define SNDRV_MIXER_OSS_PRESENT_GROUTE	(1<<2) | 
|  | #define SNDRV_MIXER_OSS_PRESENT_GVOLUME	(1<<3) | 
|  | #define SNDRV_MIXER_OSS_PRESENT_PSWITCH	(1<<4) | 
|  | #define SNDRV_MIXER_OSS_PRESENT_PROUTE	(1<<5) | 
|  | #define SNDRV_MIXER_OSS_PRESENT_PVOLUME	(1<<6) | 
|  | #define SNDRV_MIXER_OSS_PRESENT_CSWITCH	(1<<7) | 
|  | #define SNDRV_MIXER_OSS_PRESENT_CROUTE	(1<<8) | 
|  | #define SNDRV_MIXER_OSS_PRESENT_CVOLUME	(1<<9) | 
|  | #define SNDRV_MIXER_OSS_PRESENT_CAPTURE	(1<<10) | 
|  |  | 
|  | struct slot { | 
|  | unsigned int signature; | 
|  | unsigned int present; | 
|  | unsigned int channels; | 
|  | unsigned int numid[SNDRV_MIXER_OSS_ITEM_COUNT]; | 
|  | unsigned int capture_item; | 
|  | const struct snd_mixer_oss_assign_table *assigned; | 
|  | unsigned int allocated: 1; | 
|  | }; | 
|  |  | 
|  | #define ID_UNKNOWN	((unsigned int)-1) | 
|  |  | 
|  | static struct snd_kcontrol *snd_mixer_oss_test_id(struct snd_mixer_oss *mixer, const char *name, int index) | 
|  | { | 
|  | struct snd_card *card = mixer->card; | 
|  | struct snd_ctl_elem_id id; | 
|  |  | 
|  | memset(&id, 0, sizeof(id)); | 
|  | id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | 
|  | strscpy(id.name, name, sizeof(id.name)); | 
|  | id.index = index; | 
|  | return snd_ctl_find_id(card, &id); | 
|  | } | 
|  |  | 
|  | static void snd_mixer_oss_get_volume1_vol(struct snd_mixer_oss_file *fmixer, | 
|  | struct snd_mixer_oss_slot *pslot, | 
|  | unsigned int numid, | 
|  | int *left, int *right) | 
|  | { | 
|  | struct snd_ctl_elem_info *uinfo; | 
|  | struct snd_ctl_elem_value *uctl; | 
|  | struct snd_kcontrol *kctl; | 
|  | struct snd_card *card = fmixer->card; | 
|  |  | 
|  | if (numid == ID_UNKNOWN) | 
|  | return; | 
|  | down_read(&card->controls_rwsem); | 
|  | kctl = snd_ctl_find_numid(card, numid); | 
|  | if (!kctl) { | 
|  | up_read(&card->controls_rwsem); | 
|  | return; | 
|  | } | 
|  | uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); | 
|  | uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); | 
|  | if (uinfo == NULL || uctl == NULL) | 
|  | goto __unalloc; | 
|  | if (kctl->info(kctl, uinfo)) | 
|  | goto __unalloc; | 
|  | if (kctl->get(kctl, uctl)) | 
|  | goto __unalloc; | 
|  | if (uinfo->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN && | 
|  | uinfo->value.integer.min == 0 && uinfo->value.integer.max == 1) | 
|  | goto __unalloc; | 
|  | *left = snd_mixer_oss_conv1(uctl->value.integer.value[0], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[0]); | 
|  | if (uinfo->count > 1) | 
|  | *right = snd_mixer_oss_conv1(uctl->value.integer.value[1], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[1]); | 
|  | __unalloc: | 
|  | up_read(&card->controls_rwsem); | 
|  | kfree(uctl); | 
|  | kfree(uinfo); | 
|  | } | 
|  |  | 
|  | static void snd_mixer_oss_get_volume1_sw(struct snd_mixer_oss_file *fmixer, | 
|  | struct snd_mixer_oss_slot *pslot, | 
|  | unsigned int numid, | 
|  | int *left, int *right, | 
|  | int route) | 
|  | { | 
|  | struct snd_ctl_elem_info *uinfo; | 
|  | struct snd_ctl_elem_value *uctl; | 
|  | struct snd_kcontrol *kctl; | 
|  | struct snd_card *card = fmixer->card; | 
|  |  | 
|  | if (numid == ID_UNKNOWN) | 
|  | return; | 
|  | down_read(&card->controls_rwsem); | 
|  | kctl = snd_ctl_find_numid(card, numid); | 
|  | if (!kctl) { | 
|  | up_read(&card->controls_rwsem); | 
|  | return; | 
|  | } | 
|  | uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); | 
|  | uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); | 
|  | if (uinfo == NULL || uctl == NULL) | 
|  | goto __unalloc; | 
|  | if (kctl->info(kctl, uinfo)) | 
|  | goto __unalloc; | 
|  | if (kctl->get(kctl, uctl)) | 
|  | goto __unalloc; | 
|  | if (!uctl->value.integer.value[0]) { | 
|  | *left = 0; | 
|  | if (uinfo->count == 1) | 
|  | *right = 0; | 
|  | } | 
|  | if (uinfo->count > 1 && !uctl->value.integer.value[route ? 3 : 1]) | 
|  | *right = 0; | 
|  | __unalloc: | 
|  | up_read(&card->controls_rwsem); | 
|  | kfree(uctl); | 
|  | kfree(uinfo); | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_get_volume1(struct snd_mixer_oss_file *fmixer, | 
|  | struct snd_mixer_oss_slot *pslot, | 
|  | int *left, int *right) | 
|  | { | 
|  | struct slot *slot = pslot->private_data; | 
|  |  | 
|  | *left = *right = 100; | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) { | 
|  | snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) { | 
|  | snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) { | 
|  | snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right); | 
|  | } | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) { | 
|  | snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) { | 
|  | snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) { | 
|  | snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) { | 
|  | snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void snd_mixer_oss_put_volume1_vol(struct snd_mixer_oss_file *fmixer, | 
|  | struct snd_mixer_oss_slot *pslot, | 
|  | unsigned int numid, | 
|  | int left, int right) | 
|  | { | 
|  | struct snd_ctl_elem_info *uinfo; | 
|  | struct snd_ctl_elem_value *uctl; | 
|  | struct snd_kcontrol *kctl; | 
|  | struct snd_card *card = fmixer->card; | 
|  | int res; | 
|  |  | 
|  | if (numid == ID_UNKNOWN) | 
|  | return; | 
|  | down_read(&card->controls_rwsem); | 
|  | kctl = snd_ctl_find_numid(card, numid); | 
|  | if (!kctl) { | 
|  | up_read(&card->controls_rwsem); | 
|  | return; | 
|  | } | 
|  | uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); | 
|  | uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); | 
|  | if (uinfo == NULL || uctl == NULL) | 
|  | goto __unalloc; | 
|  | if (kctl->info(kctl, uinfo)) | 
|  | goto __unalloc; | 
|  | if (uinfo->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN && | 
|  | uinfo->value.integer.min == 0 && uinfo->value.integer.max == 1) | 
|  | goto __unalloc; | 
|  | uctl->value.integer.value[0] = snd_mixer_oss_conv2(left, uinfo->value.integer.min, uinfo->value.integer.max); | 
|  | if (uinfo->count > 1) | 
|  | uctl->value.integer.value[1] = snd_mixer_oss_conv2(right, uinfo->value.integer.min, uinfo->value.integer.max); | 
|  | res = kctl->put(kctl, uctl); | 
|  | if (res < 0) | 
|  | goto __unalloc; | 
|  | if (res > 0) | 
|  | snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); | 
|  | __unalloc: | 
|  | up_read(&card->controls_rwsem); | 
|  | kfree(uctl); | 
|  | kfree(uinfo); | 
|  | } | 
|  |  | 
|  | static void snd_mixer_oss_put_volume1_sw(struct snd_mixer_oss_file *fmixer, | 
|  | struct snd_mixer_oss_slot *pslot, | 
|  | unsigned int numid, | 
|  | int left, int right, | 
|  | int route) | 
|  | { | 
|  | struct snd_ctl_elem_info *uinfo; | 
|  | struct snd_ctl_elem_value *uctl; | 
|  | struct snd_kcontrol *kctl; | 
|  | struct snd_card *card = fmixer->card; | 
|  | int res; | 
|  |  | 
|  | if (numid == ID_UNKNOWN) | 
|  | return; | 
|  | down_read(&card->controls_rwsem); | 
|  | kctl = snd_ctl_find_numid(card, numid); | 
|  | if (!kctl) { | 
|  | up_read(&card->controls_rwsem); | 
|  | return; | 
|  | } | 
|  | uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); | 
|  | uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); | 
|  | if (uinfo == NULL || uctl == NULL) | 
|  | goto __unalloc; | 
|  | if (kctl->info(kctl, uinfo)) | 
|  | goto __unalloc; | 
|  | if (uinfo->count > 1) { | 
|  | uctl->value.integer.value[0] = left > 0 ? 1 : 0; | 
|  | uctl->value.integer.value[route ? 3 : 1] = right > 0 ? 1 : 0; | 
|  | if (route) { | 
|  | uctl->value.integer.value[1] = | 
|  | uctl->value.integer.value[2] = 0; | 
|  | } | 
|  | } else { | 
|  | uctl->value.integer.value[0] = (left > 0 || right > 0) ? 1 : 0; | 
|  | } | 
|  | res = kctl->put(kctl, uctl); | 
|  | if (res < 0) | 
|  | goto __unalloc; | 
|  | if (res > 0) | 
|  | snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); | 
|  | __unalloc: | 
|  | up_read(&card->controls_rwsem); | 
|  | kfree(uctl); | 
|  | kfree(uinfo); | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_put_volume1(struct snd_mixer_oss_file *fmixer, | 
|  | struct snd_mixer_oss_slot *pslot, | 
|  | int left, int right) | 
|  | { | 
|  | struct slot *slot = pslot->private_data; | 
|  |  | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) { | 
|  | snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right); | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_CVOLUME) | 
|  | snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CVOLUME], left, right); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_CVOLUME) { | 
|  | snd_mixer_oss_put_volume1_vol(fmixer, pslot, | 
|  | slot->numid[SNDRV_MIXER_OSS_ITEM_CVOLUME], left, right); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) { | 
|  | snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) { | 
|  | snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right); | 
|  | } | 
|  | if (left || right) { | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], left, right, 0); | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_CROUTE) | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], left, right, 1); | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); | 
|  | } else { | 
|  | if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) { | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) { | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], left, right, 0); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) { | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) { | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_CROUTE) { | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], left, right, 1); | 
|  | } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) { | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_get_recsrc1_sw(struct snd_mixer_oss_file *fmixer, | 
|  | struct snd_mixer_oss_slot *pslot, | 
|  | int *active) | 
|  | { | 
|  | struct slot *slot = pslot->private_data; | 
|  | int left, right; | 
|  |  | 
|  | left = right = 1; | 
|  | snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], &left, &right, 0); | 
|  | *active = (left || right) ? 1 : 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_get_recsrc1_route(struct snd_mixer_oss_file *fmixer, | 
|  | struct snd_mixer_oss_slot *pslot, | 
|  | int *active) | 
|  | { | 
|  | struct slot *slot = pslot->private_data; | 
|  | int left, right; | 
|  |  | 
|  | left = right = 1; | 
|  | snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], &left, &right, 1); | 
|  | *active = (left || right) ? 1 : 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_put_recsrc1_sw(struct snd_mixer_oss_file *fmixer, | 
|  | struct snd_mixer_oss_slot *pslot, | 
|  | int active) | 
|  | { | 
|  | struct slot *slot = pslot->private_data; | 
|  |  | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], active, active, 0); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_put_recsrc1_route(struct snd_mixer_oss_file *fmixer, | 
|  | struct snd_mixer_oss_slot *pslot, | 
|  | int active) | 
|  | { | 
|  | struct slot *slot = pslot->private_data; | 
|  |  | 
|  | snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], active, active, 1); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_get_recsrc2(struct snd_mixer_oss_file *fmixer, unsigned int *active_index) | 
|  | { | 
|  | struct snd_card *card = fmixer->card; | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | struct snd_kcontrol *kctl; | 
|  | struct snd_mixer_oss_slot *pslot; | 
|  | struct slot *slot; | 
|  | struct snd_ctl_elem_info *uinfo; | 
|  | struct snd_ctl_elem_value *uctl; | 
|  | int err, idx; | 
|  |  | 
|  | uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); | 
|  | uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); | 
|  | if (uinfo == NULL || uctl == NULL) { | 
|  | err = -ENOMEM; | 
|  | goto __free_only; | 
|  | } | 
|  | down_read(&card->controls_rwsem); | 
|  | kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0); | 
|  | if (! kctl) { | 
|  | err = -ENOENT; | 
|  | goto __unlock; | 
|  | } | 
|  | err = kctl->info(kctl, uinfo); | 
|  | if (err < 0) | 
|  | goto __unlock; | 
|  | err = kctl->get(kctl, uctl); | 
|  | if (err < 0) | 
|  | goto __unlock; | 
|  | for (idx = 0; idx < 32; idx++) { | 
|  | if (!(mixer->mask_recsrc & (1 << idx))) | 
|  | continue; | 
|  | pslot = &mixer->slots[idx]; | 
|  | slot = pslot->private_data; | 
|  | if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE) | 
|  | continue; | 
|  | if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE)) | 
|  | continue; | 
|  | if (slot->capture_item == uctl->value.enumerated.item[0]) { | 
|  | *active_index = idx; | 
|  | break; | 
|  | } | 
|  | } | 
|  | err = 0; | 
|  | __unlock: | 
|  | up_read(&card->controls_rwsem); | 
|  | __free_only: | 
|  | kfree(uctl); | 
|  | kfree(uinfo); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_put_recsrc2(struct snd_mixer_oss_file *fmixer, unsigned int active_index) | 
|  | { | 
|  | struct snd_card *card = fmixer->card; | 
|  | struct snd_mixer_oss *mixer = fmixer->mixer; | 
|  | struct snd_kcontrol *kctl; | 
|  | struct snd_mixer_oss_slot *pslot; | 
|  | struct slot *slot = NULL; | 
|  | struct snd_ctl_elem_info *uinfo; | 
|  | struct snd_ctl_elem_value *uctl; | 
|  | int err; | 
|  | unsigned int idx; | 
|  |  | 
|  | uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); | 
|  | uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); | 
|  | if (uinfo == NULL || uctl == NULL) { | 
|  | err = -ENOMEM; | 
|  | goto __free_only; | 
|  | } | 
|  | down_read(&card->controls_rwsem); | 
|  | kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0); | 
|  | if (! kctl) { | 
|  | err = -ENOENT; | 
|  | goto __unlock; | 
|  | } | 
|  | err = kctl->info(kctl, uinfo); | 
|  | if (err < 0) | 
|  | goto __unlock; | 
|  | for (idx = 0; idx < 32; idx++) { | 
|  | if (!(mixer->mask_recsrc & (1 << idx))) | 
|  | continue; | 
|  | pslot = &mixer->slots[idx]; | 
|  | slot = pslot->private_data; | 
|  | if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE) | 
|  | continue; | 
|  | if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE)) | 
|  | continue; | 
|  | if (idx == active_index) | 
|  | break; | 
|  | slot = NULL; | 
|  | } | 
|  | if (! slot) | 
|  | goto __unlock; | 
|  | for (idx = 0; idx < uinfo->count; idx++) | 
|  | uctl->value.enumerated.item[idx] = slot->capture_item; | 
|  | err = kctl->put(kctl, uctl); | 
|  | if (err > 0) | 
|  | snd_ctl_notify(fmixer->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); | 
|  | err = 0; | 
|  | __unlock: | 
|  | up_read(&card->controls_rwsem); | 
|  | __free_only: | 
|  | kfree(uctl); | 
|  | kfree(uinfo); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | struct snd_mixer_oss_assign_table { | 
|  | int oss_id; | 
|  | const char *name; | 
|  | int index; | 
|  | }; | 
|  |  | 
|  | static int snd_mixer_oss_build_test(struct snd_mixer_oss *mixer, struct slot *slot, const char *name, int index, int item) | 
|  | { | 
|  | struct snd_ctl_elem_info *info; | 
|  | struct snd_kcontrol *kcontrol; | 
|  | struct snd_card *card = mixer->card; | 
|  | int err; | 
|  |  | 
|  | down_read(&card->controls_rwsem); | 
|  | kcontrol = snd_mixer_oss_test_id(mixer, name, index); | 
|  | if (kcontrol == NULL) { | 
|  | up_read(&card->controls_rwsem); | 
|  | return 0; | 
|  | } | 
|  | info = kmalloc(sizeof(*info), GFP_KERNEL); | 
|  | if (! info) { | 
|  | up_read(&card->controls_rwsem); | 
|  | return -ENOMEM; | 
|  | } | 
|  | err = kcontrol->info(kcontrol, info); | 
|  | if (err < 0) { | 
|  | up_read(&card->controls_rwsem); | 
|  | kfree(info); | 
|  | return err; | 
|  | } | 
|  | slot->numid[item] = kcontrol->id.numid; | 
|  | up_read(&card->controls_rwsem); | 
|  | if (info->count > slot->channels) | 
|  | slot->channels = info->count; | 
|  | slot->present |= 1 << item; | 
|  | kfree(info); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void snd_mixer_oss_slot_free(struct snd_mixer_oss_slot *chn) | 
|  | { | 
|  | struct slot *p = chn->private_data; | 
|  | if (p) { | 
|  | if (p->allocated && p->assigned) { | 
|  | kfree_const(p->assigned->name); | 
|  | kfree_const(p->assigned); | 
|  | } | 
|  | kfree(p); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void mixer_slot_clear(struct snd_mixer_oss_slot *rslot) | 
|  | { | 
|  | int idx = rslot->number; /* remember this */ | 
|  | if (rslot->private_free) | 
|  | rslot->private_free(rslot); | 
|  | memset(rslot, 0, sizeof(*rslot)); | 
|  | rslot->number = idx; | 
|  | } | 
|  |  | 
|  | /* In a separate function to keep gcc 3.2 happy - do NOT merge this in | 
|  | snd_mixer_oss_build_input! */ | 
|  | static int snd_mixer_oss_build_test_all(struct snd_mixer_oss *mixer, | 
|  | const struct snd_mixer_oss_assign_table *ptr, | 
|  | struct slot *slot) | 
|  | { | 
|  | char str[64]; | 
|  | int err; | 
|  |  | 
|  | err = snd_mixer_oss_build_test(mixer, slot, ptr->name, ptr->index, | 
|  | SNDRV_MIXER_OSS_ITEM_GLOBAL); | 
|  | if (err) | 
|  | return err; | 
|  | sprintf(str, "%s Switch", ptr->name); | 
|  | err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, | 
|  | SNDRV_MIXER_OSS_ITEM_GSWITCH); | 
|  | if (err) | 
|  | return err; | 
|  | sprintf(str, "%s Route", ptr->name); | 
|  | err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, | 
|  | SNDRV_MIXER_OSS_ITEM_GROUTE); | 
|  | if (err) | 
|  | return err; | 
|  | sprintf(str, "%s Volume", ptr->name); | 
|  | err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, | 
|  | SNDRV_MIXER_OSS_ITEM_GVOLUME); | 
|  | if (err) | 
|  | return err; | 
|  | sprintf(str, "%s Playback Switch", ptr->name); | 
|  | err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, | 
|  | SNDRV_MIXER_OSS_ITEM_PSWITCH); | 
|  | if (err) | 
|  | return err; | 
|  | sprintf(str, "%s Playback Route", ptr->name); | 
|  | err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, | 
|  | SNDRV_MIXER_OSS_ITEM_PROUTE); | 
|  | if (err) | 
|  | return err; | 
|  | sprintf(str, "%s Playback Volume", ptr->name); | 
|  | err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, | 
|  | SNDRV_MIXER_OSS_ITEM_PVOLUME); | 
|  | if (err) | 
|  | return err; | 
|  | sprintf(str, "%s Capture Switch", ptr->name); | 
|  | err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, | 
|  | SNDRV_MIXER_OSS_ITEM_CSWITCH); | 
|  | if (err) | 
|  | return err; | 
|  | sprintf(str, "%s Capture Route", ptr->name); | 
|  | err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, | 
|  | SNDRV_MIXER_OSS_ITEM_CROUTE); | 
|  | if (err) | 
|  | return err; | 
|  | sprintf(str, "%s Capture Volume", ptr->name); | 
|  | err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, | 
|  | SNDRV_MIXER_OSS_ITEM_CVOLUME); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * build an OSS mixer element. | 
|  | * ptr_allocated means the entry is dynamically allocated (change via proc file). | 
|  | * when replace_old = 1, the old entry is replaced with the new one. | 
|  | */ | 
|  | static int snd_mixer_oss_build_input(struct snd_mixer_oss *mixer, | 
|  | const struct snd_mixer_oss_assign_table *ptr, | 
|  | int ptr_allocated, int replace_old) | 
|  | { | 
|  | struct slot slot; | 
|  | struct slot *pslot; | 
|  | struct snd_kcontrol *kctl; | 
|  | struct snd_mixer_oss_slot *rslot; | 
|  | char str[64]; | 
|  |  | 
|  | /* check if already assigned */ | 
|  | if (mixer->slots[ptr->oss_id].get_volume && ! replace_old) | 
|  | return 0; | 
|  |  | 
|  | memset(&slot, 0, sizeof(slot)); | 
|  | memset(slot.numid, 0xff, sizeof(slot.numid)); /* ID_UNKNOWN */ | 
|  | if (snd_mixer_oss_build_test_all(mixer, ptr, &slot)) | 
|  | return 0; | 
|  | down_read(&mixer->card->controls_rwsem); | 
|  | kctl = NULL; | 
|  | if (!ptr->index) | 
|  | kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0); | 
|  | if (kctl) { | 
|  | struct snd_ctl_elem_info *uinfo; | 
|  |  | 
|  | uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); | 
|  | if (! uinfo) { | 
|  | up_read(&mixer->card->controls_rwsem); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | if (kctl->info(kctl, uinfo)) { | 
|  | up_read(&mixer->card->controls_rwsem); | 
|  | kfree(uinfo); | 
|  | return 0; | 
|  | } | 
|  | strcpy(str, ptr->name); | 
|  | if (!strcmp(str, "Master")) | 
|  | strcpy(str, "Mix"); | 
|  | if (!strcmp(str, "Master Mono")) | 
|  | strcpy(str, "Mix Mono"); | 
|  | slot.capture_item = 0; | 
|  | if (!strcmp(uinfo->value.enumerated.name, str)) { | 
|  | slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE; | 
|  | } else { | 
|  | for (slot.capture_item = 1; slot.capture_item < uinfo->value.enumerated.items; slot.capture_item++) { | 
|  | uinfo->value.enumerated.item = slot.capture_item; | 
|  | if (kctl->info(kctl, uinfo)) { | 
|  | up_read(&mixer->card->controls_rwsem); | 
|  | kfree(uinfo); | 
|  | return 0; | 
|  | } | 
|  | if (!strcmp(uinfo->value.enumerated.name, str)) { | 
|  | slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | kfree(uinfo); | 
|  | } | 
|  | up_read(&mixer->card->controls_rwsem); | 
|  | if (slot.present != 0) { | 
|  | pslot = kmalloc(sizeof(slot), GFP_KERNEL); | 
|  | if (! pslot) | 
|  | return -ENOMEM; | 
|  | *pslot = slot; | 
|  | pslot->signature = SNDRV_MIXER_OSS_SIGNATURE; | 
|  | pslot->assigned = ptr; | 
|  | pslot->allocated = ptr_allocated; | 
|  | rslot = &mixer->slots[ptr->oss_id]; | 
|  | mixer_slot_clear(rslot); | 
|  | rslot->stereo = slot.channels > 1 ? 1 : 0; | 
|  | rslot->get_volume = snd_mixer_oss_get_volume1; | 
|  | rslot->put_volume = snd_mixer_oss_put_volume1; | 
|  | /* note: ES18xx have both Capture Source and XX Capture Volume !!! */ | 
|  | if (slot.present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) { | 
|  | rslot->get_recsrc = snd_mixer_oss_get_recsrc1_sw; | 
|  | rslot->put_recsrc = snd_mixer_oss_put_recsrc1_sw; | 
|  | } else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CROUTE) { | 
|  | rslot->get_recsrc = snd_mixer_oss_get_recsrc1_route; | 
|  | rslot->put_recsrc = snd_mixer_oss_put_recsrc1_route; | 
|  | } else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CAPTURE) { | 
|  | mixer->mask_recsrc |= 1 << ptr->oss_id; | 
|  | } | 
|  | rslot->private_data = pslot; | 
|  | rslot->private_free = snd_mixer_oss_slot_free; | 
|  | return 1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_SND_PROC_FS | 
|  | /* | 
|  | */ | 
|  | #define MIXER_VOL(name) [SOUND_MIXER_##name] = #name | 
|  | static const char * const oss_mixer_names[SNDRV_OSS_MAX_MIXERS] = { | 
|  | MIXER_VOL(VOLUME), | 
|  | MIXER_VOL(BASS), | 
|  | MIXER_VOL(TREBLE), | 
|  | MIXER_VOL(SYNTH), | 
|  | MIXER_VOL(PCM), | 
|  | MIXER_VOL(SPEAKER), | 
|  | MIXER_VOL(LINE), | 
|  | MIXER_VOL(MIC), | 
|  | MIXER_VOL(CD), | 
|  | MIXER_VOL(IMIX), | 
|  | MIXER_VOL(ALTPCM), | 
|  | MIXER_VOL(RECLEV), | 
|  | MIXER_VOL(IGAIN), | 
|  | MIXER_VOL(OGAIN), | 
|  | MIXER_VOL(LINE1), | 
|  | MIXER_VOL(LINE2), | 
|  | MIXER_VOL(LINE3), | 
|  | MIXER_VOL(DIGITAL1), | 
|  | MIXER_VOL(DIGITAL2), | 
|  | MIXER_VOL(DIGITAL3), | 
|  | MIXER_VOL(PHONEIN), | 
|  | MIXER_VOL(PHONEOUT), | 
|  | MIXER_VOL(VIDEO), | 
|  | MIXER_VOL(RADIO), | 
|  | MIXER_VOL(MONITOR), | 
|  | }; | 
|  |  | 
|  | /* | 
|  | *  /proc interface | 
|  | */ | 
|  |  | 
|  | static void snd_mixer_oss_proc_read(struct snd_info_entry *entry, | 
|  | struct snd_info_buffer *buffer) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = entry->private_data; | 
|  | int i; | 
|  |  | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | for (i = 0; i < SNDRV_OSS_MAX_MIXERS; i++) { | 
|  | struct slot *p; | 
|  |  | 
|  | if (! oss_mixer_names[i]) | 
|  | continue; | 
|  | p = (struct slot *)mixer->slots[i].private_data; | 
|  | snd_iprintf(buffer, "%s ", oss_mixer_names[i]); | 
|  | if (p && p->assigned) | 
|  | snd_iprintf(buffer, "\"%s\" %d\n", | 
|  | p->assigned->name, | 
|  | p->assigned->index); | 
|  | else | 
|  | snd_iprintf(buffer, "\"\" 0\n"); | 
|  | } | 
|  | mutex_unlock(&mixer->reg_mutex); | 
|  | } | 
|  |  | 
|  | static void snd_mixer_oss_proc_write(struct snd_info_entry *entry, | 
|  | struct snd_info_buffer *buffer) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = entry->private_data; | 
|  | char line[128], str[32], idxstr[16]; | 
|  | const char *cptr; | 
|  | unsigned int idx; | 
|  | int ch; | 
|  | struct snd_mixer_oss_assign_table *tbl; | 
|  | struct slot *slot; | 
|  |  | 
|  | while (!snd_info_get_line(buffer, line, sizeof(line))) { | 
|  | cptr = snd_info_get_str(str, line, sizeof(str)); | 
|  | for (ch = 0; ch < SNDRV_OSS_MAX_MIXERS; ch++) | 
|  | if (oss_mixer_names[ch] && strcmp(oss_mixer_names[ch], str) == 0) | 
|  | break; | 
|  | if (ch >= SNDRV_OSS_MAX_MIXERS) { | 
|  | pr_err("ALSA: mixer_oss: invalid OSS volume '%s'\n", | 
|  | str); | 
|  | continue; | 
|  | } | 
|  | cptr = snd_info_get_str(str, cptr, sizeof(str)); | 
|  | if (! *str) { | 
|  | /* remove the entry */ | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | mixer_slot_clear(&mixer->slots[ch]); | 
|  | mutex_unlock(&mixer->reg_mutex); | 
|  | continue; | 
|  | } | 
|  | snd_info_get_str(idxstr, cptr, sizeof(idxstr)); | 
|  | idx = simple_strtoul(idxstr, NULL, 10); | 
|  | if (idx >= 0x4000) { /* too big */ | 
|  | pr_err("ALSA: mixer_oss: invalid index %d\n", idx); | 
|  | continue; | 
|  | } | 
|  | mutex_lock(&mixer->reg_mutex); | 
|  | slot = (struct slot *)mixer->slots[ch].private_data; | 
|  | if (slot && slot->assigned && | 
|  | slot->assigned->index == idx && ! strcmp(slot->assigned->name, str)) | 
|  | /* not changed */ | 
|  | goto __unlock; | 
|  | tbl = kmalloc(sizeof(*tbl), GFP_KERNEL); | 
|  | if (!tbl) | 
|  | goto __unlock; | 
|  | tbl->oss_id = ch; | 
|  | tbl->name = kstrdup(str, GFP_KERNEL); | 
|  | if (! tbl->name) { | 
|  | kfree(tbl); | 
|  | goto __unlock; | 
|  | } | 
|  | tbl->index = idx; | 
|  | if (snd_mixer_oss_build_input(mixer, tbl, 1, 1) <= 0) { | 
|  | kfree(tbl->name); | 
|  | kfree(tbl); | 
|  | } | 
|  | __unlock: | 
|  | mutex_unlock(&mixer->reg_mutex); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void snd_mixer_oss_proc_init(struct snd_mixer_oss *mixer) | 
|  | { | 
|  | struct snd_info_entry *entry; | 
|  |  | 
|  | entry = snd_info_create_card_entry(mixer->card, "oss_mixer", | 
|  | mixer->card->proc_root); | 
|  | if (! entry) | 
|  | return; | 
|  | entry->content = SNDRV_INFO_CONTENT_TEXT; | 
|  | entry->mode = S_IFREG | 0644; | 
|  | entry->c.text.read = snd_mixer_oss_proc_read; | 
|  | entry->c.text.write = snd_mixer_oss_proc_write; | 
|  | entry->private_data = mixer; | 
|  | if (snd_info_register(entry) < 0) { | 
|  | snd_info_free_entry(entry); | 
|  | entry = NULL; | 
|  | } | 
|  | mixer->proc_entry = entry; | 
|  | } | 
|  |  | 
|  | static void snd_mixer_oss_proc_done(struct snd_mixer_oss *mixer) | 
|  | { | 
|  | snd_info_free_entry(mixer->proc_entry); | 
|  | mixer->proc_entry = NULL; | 
|  | } | 
|  | #else /* !CONFIG_SND_PROC_FS */ | 
|  | #define snd_mixer_oss_proc_init(mix) | 
|  | #define snd_mixer_oss_proc_done(mix) | 
|  | #endif /* CONFIG_SND_PROC_FS */ | 
|  |  | 
|  | static void snd_mixer_oss_build(struct snd_mixer_oss *mixer) | 
|  | { | 
|  | static const struct snd_mixer_oss_assign_table table[] = { | 
|  | { SOUND_MIXER_VOLUME, 	"Master",		0 }, | 
|  | { SOUND_MIXER_VOLUME, 	"Front",		0 }, /* fallback */ | 
|  | { SOUND_MIXER_BASS,	"Tone Control - Bass",	0 }, | 
|  | { SOUND_MIXER_TREBLE,	"Tone Control - Treble", 0 }, | 
|  | { SOUND_MIXER_SYNTH,	"Synth",		0 }, | 
|  | { SOUND_MIXER_SYNTH,	"FM",			0 }, /* fallback */ | 
|  | { SOUND_MIXER_SYNTH,	"Music",		0 }, /* fallback */ | 
|  | { SOUND_MIXER_PCM,	"PCM",			0 }, | 
|  | { SOUND_MIXER_SPEAKER,	"Beep", 		0 }, | 
|  | { SOUND_MIXER_SPEAKER,	"PC Speaker", 		0 }, /* fallback */ | 
|  | { SOUND_MIXER_SPEAKER,	"Speaker", 		0 }, /* fallback */ | 
|  | { SOUND_MIXER_LINE,	"Line", 		0 }, | 
|  | { SOUND_MIXER_MIC,	"Mic", 			0 }, | 
|  | { SOUND_MIXER_CD,	"CD", 			0 }, | 
|  | { SOUND_MIXER_IMIX,	"Monitor Mix", 		0 }, | 
|  | { SOUND_MIXER_ALTPCM,	"PCM",			1 }, | 
|  | { SOUND_MIXER_ALTPCM,	"Headphone",		0 }, /* fallback */ | 
|  | { SOUND_MIXER_ALTPCM,	"Wave",			0 }, /* fallback */ | 
|  | { SOUND_MIXER_RECLEV,	"-- nothing --",	0 }, | 
|  | { SOUND_MIXER_IGAIN,	"Capture",		0 }, | 
|  | { SOUND_MIXER_OGAIN,	"Playback",		0 }, | 
|  | { SOUND_MIXER_LINE1,	"Aux",			0 }, | 
|  | { SOUND_MIXER_LINE2,	"Aux",			1 }, | 
|  | { SOUND_MIXER_LINE3,	"Aux",			2 }, | 
|  | { SOUND_MIXER_DIGITAL1,	"Digital",		0 }, | 
|  | { SOUND_MIXER_DIGITAL1,	"IEC958",		0 }, /* fallback */ | 
|  | { SOUND_MIXER_DIGITAL1,	"IEC958 Optical",	0 }, /* fallback */ | 
|  | { SOUND_MIXER_DIGITAL1,	"IEC958 Coaxial",	0 }, /* fallback */ | 
|  | { SOUND_MIXER_DIGITAL2,	"Digital",		1 }, | 
|  | { SOUND_MIXER_DIGITAL3,	"Digital",		2 }, | 
|  | { SOUND_MIXER_PHONEIN,	"Phone",		0 }, | 
|  | { SOUND_MIXER_PHONEOUT,	"Master Mono",		0 }, | 
|  | { SOUND_MIXER_PHONEOUT,	"Speaker",		0 }, /*fallback*/ | 
|  | { SOUND_MIXER_PHONEOUT,	"Mono",			0 }, /*fallback*/ | 
|  | { SOUND_MIXER_PHONEOUT,	"Phone",		0 }, /* fallback */ | 
|  | { SOUND_MIXER_VIDEO,	"Video",		0 }, | 
|  | { SOUND_MIXER_RADIO,	"Radio",		0 }, | 
|  | { SOUND_MIXER_MONITOR,	"Monitor",		0 } | 
|  | }; | 
|  | unsigned int idx; | 
|  |  | 
|  | for (idx = 0; idx < ARRAY_SIZE(table); idx++) | 
|  | snd_mixer_oss_build_input(mixer, &table[idx], 0, 0); | 
|  | if (mixer->mask_recsrc) { | 
|  | mixer->get_recsrc = snd_mixer_oss_get_recsrc2; | 
|  | mixer->put_recsrc = snd_mixer_oss_put_recsrc2; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * | 
|  | */ | 
|  |  | 
|  | static int snd_mixer_oss_free1(void *private) | 
|  | { | 
|  | struct snd_mixer_oss *mixer = private; | 
|  | struct snd_card *card; | 
|  | int idx; | 
|  |  | 
|  | if (!mixer) | 
|  | return 0; | 
|  | card = mixer->card; | 
|  | if (snd_BUG_ON(mixer != card->mixer_oss)) | 
|  | return -ENXIO; | 
|  | card->mixer_oss = NULL; | 
|  | for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) { | 
|  | struct snd_mixer_oss_slot *chn = &mixer->slots[idx]; | 
|  | if (chn->private_free) | 
|  | chn->private_free(chn); | 
|  | } | 
|  | kfree(mixer); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) | 
|  | { | 
|  | struct snd_mixer_oss *mixer; | 
|  |  | 
|  | if (cmd == SND_MIXER_OSS_NOTIFY_REGISTER) { | 
|  | int idx, err; | 
|  |  | 
|  | mixer = kcalloc(2, sizeof(*mixer), GFP_KERNEL); | 
|  | if (mixer == NULL) | 
|  | return -ENOMEM; | 
|  | mutex_init(&mixer->reg_mutex); | 
|  | err = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, | 
|  | card, 0, | 
|  | &snd_mixer_oss_f_ops, card); | 
|  | if (err < 0) { | 
|  | dev_err(card->dev, | 
|  | "unable to register OSS mixer device %i:%i\n", | 
|  | card->number, 0); | 
|  | kfree(mixer); | 
|  | return err; | 
|  | } | 
|  | mixer->oss_dev_alloc = 1; | 
|  | mixer->card = card; | 
|  | if (*card->mixername) | 
|  | strscpy(mixer->name, card->mixername, sizeof(mixer->name)); | 
|  | else | 
|  | snprintf(mixer->name, sizeof(mixer->name), | 
|  | "mixer%i", card->number); | 
|  | #ifdef SNDRV_OSS_INFO_DEV_MIXERS | 
|  | snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIXERS, | 
|  | card->number, | 
|  | mixer->name); | 
|  | #endif | 
|  | for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) | 
|  | mixer->slots[idx].number = idx; | 
|  | card->mixer_oss = mixer; | 
|  | snd_mixer_oss_build(mixer); | 
|  | snd_mixer_oss_proc_init(mixer); | 
|  | } else { | 
|  | mixer = card->mixer_oss; | 
|  | if (mixer == NULL) | 
|  | return 0; | 
|  | if (mixer->oss_dev_alloc) { | 
|  | #ifdef SNDRV_OSS_INFO_DEV_MIXERS | 
|  | snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIXERS, mixer->card->number); | 
|  | #endif | 
|  | snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, mixer->card, 0); | 
|  | mixer->oss_dev_alloc = 0; | 
|  | } | 
|  | if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT) | 
|  | return 0; | 
|  | snd_mixer_oss_proc_done(mixer); | 
|  | return snd_mixer_oss_free1(mixer); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int __init alsa_mixer_oss_init(void) | 
|  | { | 
|  | struct snd_card *card; | 
|  | int idx; | 
|  |  | 
|  | snd_mixer_oss_notify_callback = snd_mixer_oss_notify_handler; | 
|  | for (idx = 0; idx < SNDRV_CARDS; idx++) { | 
|  | card = snd_card_ref(idx); | 
|  | if (card) { | 
|  | snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_REGISTER); | 
|  | snd_card_unref(card); | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void __exit alsa_mixer_oss_exit(void) | 
|  | { | 
|  | struct snd_card *card; | 
|  | int idx; | 
|  |  | 
|  | snd_mixer_oss_notify_callback = NULL; | 
|  | for (idx = 0; idx < SNDRV_CARDS; idx++) { | 
|  | card = snd_card_ref(idx); | 
|  | if (card) { | 
|  | snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_FREE); | 
|  | snd_card_unref(card); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | module_init(alsa_mixer_oss_init) | 
|  | module_exit(alsa_mixer_oss_exit) |