Commit a5baa395 authored by Jaroslav Kysela's avatar Jaroslav Kysela

ALSA CVS update - Jaroslav Kysela <perex@suse.cz>

Control Midlevel,ALSA Core,EMU8000 driver,SB16/AWE driver
EMU10K1/EMU10K2 driver
- added support for user control elements (untested)
- fixed locking for snd_ctl_remove() function
parent f4855c43
......@@ -684,7 +684,7 @@ struct sndrv_timer_tread {
* *
****************************************************************************/
#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 2)
#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 3)
struct sndrv_ctl_card_info {
int card; /* card number */
......@@ -729,6 +729,7 @@ enum sndrv_ctl_elem_iface {
#define SNDRV_CTL_ELEM_ACCESS_INACTIVE (1<<8) /* control does actually nothing, but may be updated */
#define SNDRV_CTL_ELEM_ACCESS_LOCK (1<<9) /* write lock */
#define SNDRV_CTL_ELEM_ACCESS_OWNER (1<<10) /* write lock owner */
#define SNDRV_CTL_ELEM_ACCESS_USER (1<<29) /* user space element */
#define SNDRV_CTL_ELEM_ACCESS_DINDIRECT (1<<30) /* indirect access for matrix dimensions in the info structure */
#define SNDRV_CTL_ELEM_ACCESS_INDIRECT (1<<31) /* indirect access for element value in the value structure */
......@@ -825,6 +826,9 @@ enum {
SNDRV_CTL_IOCTL_ELEM_LOCK = _IOW('U', 0x14, struct sndrv_ctl_elem_id),
SNDRV_CTL_IOCTL_ELEM_UNLOCK = _IOW('U', 0x15, struct sndrv_ctl_elem_id),
SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS = _IOWR('U', 0x16, int),
SNDRV_CTL_IOCTL_ELEM_ADD = _IOWR('U', 0x17, struct sndrv_ctl_elem_info),
SNDRV_CTL_IOCTL_ELEM_REPLACE = _IOWR('U', 0x18, struct sndrv_ctl_elem_info),
SNDRV_CTL_IOCTL_ELEM_REMOVE = _IOWR('U', 0x19, struct sndrv_ctl_elem_id),
SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE = _IOWR('U', 0x20, int),
SNDRV_CTL_IOCTL_HWDEP_INFO = _IOR('U', 0x21, struct sndrv_hwdep_info),
SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE = _IOR('U', 0x30, int),
......
......@@ -37,6 +37,9 @@ typedef struct _snd_kctl_ioctl {
#define snd_kctl_ioctl(n) list_entry(n, snd_kctl_ioctl_t, list)
/* find id without lock */
static snd_kcontrol_t *_ctl_find_id(snd_card_t * card, snd_ctl_elem_id_t *id);
static DECLARE_RWSEM(snd_ioctl_rwsem);
static LIST_HEAD(snd_control_ioctls);
......@@ -262,6 +265,8 @@ void snd_ctl_free_one(snd_kcontrol_t * kcontrol)
* snd_ctl_new1() to the given card.
*
* Returns zero if successful, or a negative error code on failure.
*
* It frees automatically the control which cannot be added.
*/
int snd_ctl_add(snd_card_t * card, snd_kcontrol_t * kcontrol)
{
......@@ -270,13 +275,23 @@ int snd_ctl_add(snd_card_t * card, snd_kcontrol_t * kcontrol)
snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL);
snd_assert(kcontrol->info != NULL, return -EINVAL);
id = kcontrol->id;
down_write(&card->controls_rwsem);
if (_ctl_find_id(card, &id)) {
up_write(&card->controls_rwsem);
snd_ctl_free_one(kcontrol);
return -EBUSY;
}
if (card->last_numid > 0x80000000 && card->last_numid + kcontrol->count < 0x80000000) {
up_write(&card->controls_rwsem);
snd_ctl_free_one(kcontrol);
return -ENOMEM; /* FIXME: find a hole */
}
list_add_tail(&kcontrol->list, &card->controls);
card->controls_count += kcontrol->count;
kcontrol->id.numid = card->last_numid + 1;
card->last_numid += kcontrol->count;
up_write(&card->controls_rwsem);
id = kcontrol->id;
for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
return 0;
......@@ -288,7 +303,8 @@ int snd_ctl_add(snd_card_t * card, snd_kcontrol_t * kcontrol)
* @kcontrol: the control instance to remove
*
* Removes the control from the card and then releases the instance.
* You don't need to call snd_ctl_free_one().
* You don't need to call snd_ctl_free_one(). You must be in
* the write lock - down_write(&card->controls_rwsem).
*
* Returns 0 if successful, or a negative error code on failure.
*/
......@@ -298,10 +314,8 @@ int snd_ctl_remove(snd_card_t * card, snd_kcontrol_t * kcontrol)
unsigned int idx;
snd_runtime_check(card != NULL && kcontrol != NULL, return -EINVAL);
down_write(&card->controls_rwsem);
list_del(&kcontrol->list);
card->controls_count -= kcontrol->count;
up_write(&card->controls_rwsem);
id = kcontrol->id;
for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_REMOVE, &id);
......@@ -322,15 +336,50 @@ int snd_ctl_remove(snd_card_t * card, snd_kcontrol_t * kcontrol)
int snd_ctl_remove_id(snd_card_t * card, snd_ctl_elem_id_t *id)
{
snd_kcontrol_t *kctl;
int ret;
kctl = snd_ctl_find_id(card, id);
if (kctl == NULL)
down_write(&card->controls_rwsem);
kctl = _ctl_find_id(card, id);
if (kctl == NULL) {
up_write(&card->controls_rwsem);
return -ENOENT;
return snd_ctl_remove(card, kctl);
}
ret = snd_ctl_remove(card, kctl);
up_write(&card->controls_rwsem);
return ret;
}
static snd_kcontrol_t *_ctl_find_id
(snd_card_t * card, snd_ctl_elem_id_t *id); /* w/o lock */
/**
* snd_ctl_remove_unlocked_id - remove the unlocked control of the given id and release it
* @file: active control handle
* @id: the control id to remove
*
* Finds the control instance with the given id, removes it from the
* card list and releases it.
*
* Returns 0 if successful, or a negative error code on failure.
*/
static int snd_ctl_remove_unlocked_id(snd_ctl_file_t * file, snd_ctl_elem_id_t *id)
{
snd_card_t *card = file->card;
snd_kcontrol_t *kctl;
int idx, ret;
down_write(&card->controls_rwsem);
kctl = _ctl_find_id(card, id);
if (kctl == NULL) {
up_write(&card->controls_rwsem);
return -ENOENT;
}
for (idx = 0; idx < kctl->count; idx++)
if (kctl->vd[idx].owner != NULL && kctl->vd[idx].owner != file) {
up_write(&card->controls_rwsem);
return -EBUSY;
}
ret = snd_ctl_remove(card, kctl);
up_write(&card->controls_rwsem);
return ret;
}
/**
* snd_ctl_rename_id - replace the id of a control on the card
......@@ -564,7 +613,6 @@ static int snd_ctl_elem_info(snd_ctl_file_t *ctl, snd_ctl_elem_info_t *_info)
static int snd_ctl_elem_read(snd_card_t *card, snd_ctl_elem_value_t *_control)
{
snd_ctl_elem_value_t *control;
snd_kcontrol_t *kctl;
snd_kcontrol_volatile_t *vd;
......@@ -711,6 +759,196 @@ static int snd_ctl_elem_unlock(snd_ctl_file_t *file, snd_ctl_elem_id_t *_id)
return result;
}
struct user_element {
enum sndrv_ctl_elem_type type; /* element type */
unsigned int elem_count; /* count of elements */
union {
struct {
unsigned int items;
} enumerated;
} u;
void *elem_data; /* element data */
unsigned long elem_data_size; /* size of element data in bytes */
void *priv_data; /* private data (like strings for enumerated type) */
unsigned long priv_data_size; /* size of private data in bytes */
unsigned short dimen_count; /* count of dimensions */
unsigned short dimen[0]; /* array of dimensions */
};
static int snd_ctl_elem_user_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
struct user_element *ue = kcontrol->private_data;
uinfo->type = ue->type;
uinfo->count = ue->elem_count;
if (ue->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
uinfo->value.enumerated.items = ue->u.enumerated.items;
if (uinfo->value.enumerated.item >= ue->u.enumerated.items)
uinfo->value.enumerated.item = 0;
strlcpy(uinfo->value.enumerated.name,
(char *)ue->priv_data + uinfo->value.enumerated.item * 64,
64);
}
return 0;
}
static int snd_ctl_elem_user_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
struct user_element *ue = kcontrol->private_data;
memcpy(&ucontrol->value, ue->elem_data, ue->elem_data_size);
return 0;
}
static int snd_ctl_elem_user_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
{
int change;
struct user_element *ue = kcontrol->private_data;
change = memcmp(&ucontrol->value, ue->elem_data, ue->elem_data_size);
memcpy(ue->elem_data, &ucontrol->value, ue->elem_data_size);
return !!change;
}
static void snd_ctl_elem_user_free(snd_kcontrol_t * kcontrol)
{
kfree(kcontrol->private_data);
}
static int snd_ctl_elem_add(snd_ctl_file_t *file, snd_ctl_elem_info_t *_info, int replace)
{
snd_card_t *card = file->card;
snd_ctl_elem_info_t info;
snd_kcontrol_t kctl, *_kctl;
unsigned int access;
long private_size, dimen_size, extra_size;
struct user_element *ue;
int idx, err;
if (copy_from_user(&info, _info, sizeof(info)))
return -EFAULT;
if (info.count > 1024)
return -EINVAL;
access = info.access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
(info.access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE|
SNDRV_CTL_ELEM_ACCESS_DINDIRECT|SNDRV_CTL_ELEM_ACCESS_INDIRECT));
if (access & (SNDRV_CTL_ELEM_ACCESS_DINDIRECT | SNDRV_CTL_ELEM_ACCESS_INDIRECT))
return -EINVAL;
info.id.numid = 0;
memset(&kctl, 0, sizeof(kctl));
down_write(&card->controls_rwsem);
if (!!((_kctl = _ctl_find_id(card, &info.id)) != NULL) ^ replace) {
up_write(&card->controls_rwsem);
return !replace ? -EBUSY : -ENOENT;
}
if (replace) {
err = snd_ctl_remove(card, _kctl);
if (err < 0) {
up_write(&card->controls_rwsem);
return err;
}
}
up_write(&card->controls_rwsem);
memcpy(&kctl.id, &info.id, sizeof(info.id));
kctl.count = info.owner ? info.owner : 1;
access |= SNDRV_CTL_ELEM_ACCESS_USER;
kctl.info = snd_ctl_elem_user_info;
if (access & SNDRV_CTL_ELEM_ACCESS_READ)
kctl.get = snd_ctl_elem_user_get;
if (access & SNDRV_CTL_ELEM_ACCESS_WRITE)
kctl.put = snd_ctl_elem_user_put;
extra_size = 0;
switch (info.type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
private_size = sizeof(char);
if (info.count > 128)
return -EINVAL;
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER:
private_size = sizeof(long);
if (info.count > 128)
return -EINVAL;
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
private_size = sizeof(long long);
if (info.count > 64)
return -EINVAL;
break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
private_size = sizeof(unsigned int);
if (info.count > 128)
return -EINVAL;
if (info.value.enumerated.items > 1024)
return -EINVAL;
extra_size = info.value.enumerated.items * 64;
break;
case SNDRV_CTL_ELEM_TYPE_BYTES:
private_size = sizeof(unsigned char);
if (info.count > 512)
return -EINVAL;
break;
case SNDRV_CTL_ELEM_TYPE_IEC958:
private_size = sizeof(struct sndrv_aes_iec958);
if (info.count != 1)
return -EINVAL;
break;
default:
return -EINVAL;
}
private_size *= info.count;
if (private_size > 1024 * 1024)
return -EINVAL;
dimen_size = 0;
if (!(info.access & SNDRV_CTL_ELEM_ACCESS_DINDIRECT))
for (idx = 0; idx < 4 && info.dimen.d[idx]; idx++)
dimen_size += sizeof(unsigned short);
ue = snd_kcalloc(sizeof(struct user_element) + dimen_size + private_size + extra_size, GFP_KERNEL);
if (ue == NULL)
return -ENOMEM;
ue->type = info.type;
ue->elem_count = info.count;
if (!(info.access & SNDRV_CTL_ELEM_ACCESS_DINDIRECT)) {
for (idx = 0; idx < 4 && info.dimen.d[idx]; idx++)
ue->dimen[idx] = info.dimen.d[idx];
ue->dimen_count = dimen_size / sizeof(unsigned short);
}
ue->elem_data = (char *)ue + sizeof(ue) + dimen_size;
ue->elem_data_size = private_size;
if (extra_size) {
ue->priv_data = (char *)ue + sizeof(ue) + dimen_size + private_size;
ue->priv_data_size = extra_size;
if (ue->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
if (copy_from_user(ue->priv_data, *(char **)info.value.enumerated.name, extra_size))
return -EFAULT;
ue->u.enumerated.items = info.value.enumerated.items;
}
}
kctl.private_free = snd_ctl_elem_user_free;
_kctl = snd_ctl_new(&kctl, access);
if (_kctl == NULL) {
kfree(_kctl->private_data);
return -ENOMEM;
}
_kctl->private_data = ue;
for (idx = 0; idx < _kctl->count; idx++)
_kctl->vd[idx].owner = file;
err = snd_ctl_add(card, _kctl);
if (err < 0) {
snd_ctl_free_one(_kctl);
return err;
}
return 0;
}
static int snd_ctl_elem_remove(snd_ctl_file_t *file, snd_ctl_elem_id_t *_id)
{
snd_ctl_elem_id_t id;
if (copy_from_user(&id, _id, sizeof(id)))
return -EFAULT;
return snd_ctl_remove_unlocked_id(file, &id);
}
static int snd_ctl_subscribe_events(snd_ctl_file_t *file, int *ptr)
{
int subscribe;
......@@ -761,6 +999,12 @@ static int snd_ctl_ioctl(struct inode *inode, struct file *file,
return snd_ctl_elem_lock(ctl, (snd_ctl_elem_id_t *) arg);
case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
return snd_ctl_elem_unlock(ctl, (snd_ctl_elem_id_t *) arg);
case SNDRV_CTL_IOCTL_ELEM_ADD:
return snd_ctl_elem_add(ctl, (snd_ctl_elem_info_t *) arg, 0);
case SNDRV_CTL_IOCTL_ELEM_REPLACE:
return snd_ctl_elem_add(ctl, (snd_ctl_elem_info_t *) arg, 1);
case SNDRV_CTL_IOCTL_ELEM_REMOVE:
return snd_ctl_elem_remove(ctl, (snd_ctl_elem_id_t *) arg);
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
return snd_ctl_subscribe_events(ctl, (int *) arg);
case SNDRV_CTL_IOCTL_POWER:
......@@ -995,9 +1239,11 @@ int snd_ctl_unregister(snd_card_t *card)
snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO);
if ((err = snd_unregister_device(SNDRV_DEVICE_TYPE_CONTROL, card, 0)) < 0)
return err;
down_write(&card->controls_rwsem);
while (!list_empty(&card->controls)) {
control = snd_kcontrol(card->controls.next);
snd_ctl_remove(card, control);
}
up_write(&card->controls_rwsem);
return 0;
}
......@@ -1044,8 +1044,10 @@ snd_emu8000_create_mixer(snd_card_t *card, emu8000_t *emu)
__error:
for (i = 0; i < EMU8000_NUM_CONTROLS; i++) {
down_write(&card->controls_rwsem);
if (emu->controls[i])
snd_ctl_remove(card, emu->controls[i]);
up_write(&card->controls_rwsem);
}
return err;
}
......
......@@ -1059,10 +1059,12 @@ static void snd_sb_qsound_destroy(snd_sb_csp_t * p)
card = p->chip->card;
down_write(&card->controls_rwsem);
if (p->qsound_switch)
snd_ctl_remove(card, p->qsound_switch);
if (p->qsound_space)
snd_ctl_remove(card, p->qsound_space);
up_write(&card->controls_rwsem);
/* cancel pending transfer of QSound parameters */
spin_lock_irqsave (&p->q_lock, flags);
......
......@@ -1009,13 +1009,16 @@ static void snd_emu10k1_del_controls(emu10k1_t *emu, emu10k1_fx8010_code_t *icod
unsigned int i;
snd_ctl_elem_id_t *_id, id;
snd_emu10k1_fx8010_ctl_t *ctl;
snd_card_t *card = emu->card;
for (i = 0, _id = icode->gpr_del_controls;
i < icode->gpr_del_control_count; i++, _id++) {
snd_runtime_check(copy_from_user(&id, _id, sizeof(id)) == 0, continue);
down_write(&card->controls_rwsem);
ctl = snd_emu10k1_look_for_ctl(emu, &id);
snd_runtime_check(ctl == NULL, continue);
snd_ctl_remove(emu->card, ctl->kcontrol);
if (ctl)
snd_ctl_remove(card, ctl->kcontrol);
up_write(&card->controls_rwsem);
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment