[ALSA] Control API - more robust TLV implementation
- added callback option - added READ/WRITE/COMMAND flags to access member - added WRITE/COMMAND ioctls - added SNDRV_CTL_EVENT_MASK_TLV for TLV change notifications - added TLV support to ELEM_ADD ioctl Signed-off-by: Jaroslav Kysela <perex@suse.cz>
This commit is contained in:
parent
6bbe13ecbb
commit
8aa9b586e4
@ -727,10 +727,15 @@ typedef int __bitwise snd_ctl_elem_iface_t;
|
|||||||
#define SNDRV_CTL_ELEM_ACCESS_WRITE (1<<1)
|
#define SNDRV_CTL_ELEM_ACCESS_WRITE (1<<1)
|
||||||
#define SNDRV_CTL_ELEM_ACCESS_READWRITE (SNDRV_CTL_ELEM_ACCESS_READ|SNDRV_CTL_ELEM_ACCESS_WRITE)
|
#define SNDRV_CTL_ELEM_ACCESS_READWRITE (SNDRV_CTL_ELEM_ACCESS_READ|SNDRV_CTL_ELEM_ACCESS_WRITE)
|
||||||
#define SNDRV_CTL_ELEM_ACCESS_VOLATILE (1<<2) /* control value may be changed without a notification */
|
#define SNDRV_CTL_ELEM_ACCESS_VOLATILE (1<<2) /* control value may be changed without a notification */
|
||||||
#define SNDRV_CTL_ELEM_ACCESS_TIMESTAMP (1<<2) /* when was control changed */
|
#define SNDRV_CTL_ELEM_ACCESS_TIMESTAMP (1<<3) /* when was control changed */
|
||||||
|
#define SNDRV_CTL_ELEM_ACCESS_TLV_READ (1<<4) /* TLV read is possible */
|
||||||
|
#define SNDRV_CTL_ELEM_ACCESS_TLV_WRITE (1<<5) /* TLV write is possible */
|
||||||
|
#define SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE (SNDRV_CTL_ELEM_ACCESS_TLV_READ|SNDRV_CTL_ELEM_ACCESS_TLV_WRITE)
|
||||||
|
#define SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND (1<<6) /* TLV command is possible */
|
||||||
#define SNDRV_CTL_ELEM_ACCESS_INACTIVE (1<<8) /* control does actually nothing, but may be updated */
|
#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_LOCK (1<<9) /* write lock */
|
||||||
#define SNDRV_CTL_ELEM_ACCESS_OWNER (1<<10) /* write lock owner */
|
#define SNDRV_CTL_ELEM_ACCESS_OWNER (1<<10) /* write lock owner */
|
||||||
|
#define SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK (1<<28) /* kernel use a TLV callback */
|
||||||
#define SNDRV_CTL_ELEM_ACCESS_USER (1<<29) /* user space element */
|
#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_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 */
|
#define SNDRV_CTL_ELEM_ACCESS_INDIRECT (1<<31) /* indirect access for element value in the value structure */
|
||||||
@ -838,6 +843,8 @@ enum {
|
|||||||
SNDRV_CTL_IOCTL_ELEM_REPLACE = _IOWR('U', 0x18, struct snd_ctl_elem_info),
|
SNDRV_CTL_IOCTL_ELEM_REPLACE = _IOWR('U', 0x18, struct snd_ctl_elem_info),
|
||||||
SNDRV_CTL_IOCTL_ELEM_REMOVE = _IOWR('U', 0x19, struct snd_ctl_elem_id),
|
SNDRV_CTL_IOCTL_ELEM_REMOVE = _IOWR('U', 0x19, struct snd_ctl_elem_id),
|
||||||
SNDRV_CTL_IOCTL_TLV_READ = _IOWR('U', 0x1a, struct snd_ctl_tlv),
|
SNDRV_CTL_IOCTL_TLV_READ = _IOWR('U', 0x1a, struct snd_ctl_tlv),
|
||||||
|
SNDRV_CTL_IOCTL_TLV_WRITE = _IOWR('U', 0x1b, struct snd_ctl_tlv),
|
||||||
|
SNDRV_CTL_IOCTL_TLV_COMMAND = _IOWR('U', 0x1c, struct snd_ctl_tlv),
|
||||||
SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE = _IOWR('U', 0x20, int),
|
SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE = _IOWR('U', 0x20, int),
|
||||||
SNDRV_CTL_IOCTL_HWDEP_INFO = _IOR('U', 0x21, struct snd_hwdep_info),
|
SNDRV_CTL_IOCTL_HWDEP_INFO = _IOR('U', 0x21, struct snd_hwdep_info),
|
||||||
SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE = _IOR('U', 0x30, int),
|
SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE = _IOR('U', 0x30, int),
|
||||||
@ -862,6 +869,7 @@ enum sndrv_ctl_event_type {
|
|||||||
#define SNDRV_CTL_EVENT_MASK_VALUE (1<<0) /* element value was changed */
|
#define SNDRV_CTL_EVENT_MASK_VALUE (1<<0) /* element value was changed */
|
||||||
#define SNDRV_CTL_EVENT_MASK_INFO (1<<1) /* element info was changed */
|
#define SNDRV_CTL_EVENT_MASK_INFO (1<<1) /* element info was changed */
|
||||||
#define SNDRV_CTL_EVENT_MASK_ADD (1<<2) /* element was added */
|
#define SNDRV_CTL_EVENT_MASK_ADD (1<<2) /* element was added */
|
||||||
|
#define SNDRV_CTL_EVENT_MASK_TLV (1<<3) /* element TLV tree was changed */
|
||||||
#define SNDRV_CTL_EVENT_MASK_REMOVE (~0U) /* element was removed */
|
#define SNDRV_CTL_EVENT_MASK_REMOVE (~0U) /* element was removed */
|
||||||
|
|
||||||
struct snd_ctl_event {
|
struct snd_ctl_event {
|
||||||
|
@ -30,6 +30,11 @@ struct snd_kcontrol;
|
|||||||
typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo);
|
typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo);
|
||||||
typedef int (snd_kcontrol_get_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
|
typedef int (snd_kcontrol_get_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
|
||||||
typedef int (snd_kcontrol_put_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
|
typedef int (snd_kcontrol_put_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
|
||||||
|
typedef int (snd_kcontrol_tlv_rw_t)(struct snd_kcontrol *kcontrol,
|
||||||
|
int op_flag, /* 0=read,1=write,-1=command */
|
||||||
|
unsigned int size,
|
||||||
|
unsigned int __user *tlv);
|
||||||
|
|
||||||
|
|
||||||
struct snd_kcontrol_new {
|
struct snd_kcontrol_new {
|
||||||
snd_ctl_elem_iface_t iface; /* interface identifier */
|
snd_ctl_elem_iface_t iface; /* interface identifier */
|
||||||
@ -42,7 +47,10 @@ struct snd_kcontrol_new {
|
|||||||
snd_kcontrol_info_t *info;
|
snd_kcontrol_info_t *info;
|
||||||
snd_kcontrol_get_t *get;
|
snd_kcontrol_get_t *get;
|
||||||
snd_kcontrol_put_t *put;
|
snd_kcontrol_put_t *put;
|
||||||
unsigned int *tlv;
|
union {
|
||||||
|
snd_kcontrol_tlv_rw_t *c;
|
||||||
|
unsigned int *p;
|
||||||
|
} tlv;
|
||||||
unsigned long private_value;
|
unsigned long private_value;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,7 +67,11 @@ struct snd_kcontrol {
|
|||||||
snd_kcontrol_info_t *info;
|
snd_kcontrol_info_t *info;
|
||||||
snd_kcontrol_get_t *get;
|
snd_kcontrol_get_t *get;
|
||||||
snd_kcontrol_put_t *put;
|
snd_kcontrol_put_t *put;
|
||||||
unsigned int *tlv;
|
snd_kcontrol_tlv_rw_t *tlv_rw;
|
||||||
|
union {
|
||||||
|
snd_kcontrol_tlv_rw_t *c;
|
||||||
|
unsigned int *p;
|
||||||
|
} tlv;
|
||||||
unsigned long private_value;
|
unsigned long private_value;
|
||||||
void *private_data;
|
void *private_data;
|
||||||
void (*private_free)(struct snd_kcontrol *kcontrol);
|
void (*private_free)(struct snd_kcontrol *kcontrol);
|
||||||
|
@ -236,12 +236,16 @@ struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
|
|||||||
kctl.id.index = ncontrol->index;
|
kctl.id.index = ncontrol->index;
|
||||||
kctl.count = ncontrol->count ? ncontrol->count : 1;
|
kctl.count = ncontrol->count ? ncontrol->count : 1;
|
||||||
access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
|
access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
|
||||||
(ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE|
|
(ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|
|
||||||
SNDRV_CTL_ELEM_ACCESS_DINDIRECT|SNDRV_CTL_ELEM_ACCESS_INDIRECT));
|
SNDRV_CTL_ELEM_ACCESS_INACTIVE|
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_DINDIRECT|
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_INDIRECT|
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE|
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK));
|
||||||
kctl.info = ncontrol->info;
|
kctl.info = ncontrol->info;
|
||||||
kctl.get = ncontrol->get;
|
kctl.get = ncontrol->get;
|
||||||
kctl.put = ncontrol->put;
|
kctl.put = ncontrol->put;
|
||||||
kctl.tlv = ncontrol->tlv;
|
kctl.tlv.p = ncontrol->tlv.p;
|
||||||
kctl.private_value = ncontrol->private_value;
|
kctl.private_value = ncontrol->private_value;
|
||||||
kctl.private_data = private_data;
|
kctl.private_data = private_data;
|
||||||
return snd_ctl_new(&kctl, access);
|
return snd_ctl_new(&kctl, access);
|
||||||
@ -883,6 +887,8 @@ struct user_element {
|
|||||||
struct snd_ctl_elem_info info;
|
struct snd_ctl_elem_info info;
|
||||||
void *elem_data; /* element data */
|
void *elem_data; /* element data */
|
||||||
unsigned long elem_data_size; /* size of element data in bytes */
|
unsigned long elem_data_size; /* size of element data in bytes */
|
||||||
|
void *tlv_data; /* TLV data */
|
||||||
|
unsigned long tlv_data_size; /* TLV data size */
|
||||||
void *priv_data; /* private data (like strings for enumerated type) */
|
void *priv_data; /* private data (like strings for enumerated type) */
|
||||||
unsigned long priv_data_size; /* size of private data in bytes */
|
unsigned long priv_data_size; /* size of private data in bytes */
|
||||||
};
|
};
|
||||||
@ -917,9 +923,46 @@ static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol,
|
|||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol,
|
||||||
|
int op_flag,
|
||||||
|
unsigned int size,
|
||||||
|
unsigned int __user *tlv)
|
||||||
|
{
|
||||||
|
struct user_element *ue = kcontrol->private_data;
|
||||||
|
int change = 0;
|
||||||
|
void *new_data;
|
||||||
|
|
||||||
|
if (op_flag > 0) {
|
||||||
|
if (size > 1024 * 128) /* sane value */
|
||||||
|
return -EINVAL;
|
||||||
|
new_data = kmalloc(size, GFP_KERNEL);
|
||||||
|
if (new_data == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
if (copy_from_user(new_data, tlv, size)) {
|
||||||
|
kfree(new_data);
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
change = ue->tlv_data_size != size;
|
||||||
|
if (!change)
|
||||||
|
change = memcmp(ue->tlv_data, new_data, size);
|
||||||
|
kfree(ue->tlv_data);
|
||||||
|
ue->tlv_data = new_data;
|
||||||
|
ue->tlv_data_size = size;
|
||||||
|
} else {
|
||||||
|
if (size < ue->tlv_data_size)
|
||||||
|
return -ENOSPC;
|
||||||
|
if (copy_to_user(tlv, ue->tlv_data, ue->tlv_data_size))
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
|
||||||
static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol)
|
static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol)
|
||||||
{
|
{
|
||||||
kfree(kcontrol->private_data);
|
struct user_element *ue = kcontrol->private_data;
|
||||||
|
if (ue->tlv_data)
|
||||||
|
kfree(ue->tlv_data);
|
||||||
|
kfree(ue);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int snd_ctl_elem_add(struct snd_ctl_file *file,
|
static int snd_ctl_elem_add(struct snd_ctl_file *file,
|
||||||
@ -938,7 +981,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
|
access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
|
||||||
(info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|
|
(info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|
|
||||||
SNDRV_CTL_ELEM_ACCESS_INACTIVE));
|
SNDRV_CTL_ELEM_ACCESS_INACTIVE|
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE));
|
||||||
info->id.numid = 0;
|
info->id.numid = 0;
|
||||||
memset(&kctl, 0, sizeof(kctl));
|
memset(&kctl, 0, sizeof(kctl));
|
||||||
down_write(&card->controls_rwsem);
|
down_write(&card->controls_rwsem);
|
||||||
@ -964,6 +1008,10 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
|
|||||||
kctl.get = snd_ctl_elem_user_get;
|
kctl.get = snd_ctl_elem_user_get;
|
||||||
if (access & SNDRV_CTL_ELEM_ACCESS_WRITE)
|
if (access & SNDRV_CTL_ELEM_ACCESS_WRITE)
|
||||||
kctl.put = snd_ctl_elem_user_put;
|
kctl.put = snd_ctl_elem_user_put;
|
||||||
|
if (access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) {
|
||||||
|
kctl.tlv.c = snd_ctl_elem_user_tlv;
|
||||||
|
access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
|
||||||
|
}
|
||||||
switch (info->type) {
|
switch (info->type) {
|
||||||
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
||||||
private_size = sizeof(char);
|
private_size = sizeof(char);
|
||||||
@ -1068,11 +1116,14 @@ static int snd_ctl_subscribe_events(struct snd_ctl_file *file, int __user *ptr)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int snd_ctl_tlv_read(struct snd_card *card,
|
static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,
|
||||||
struct snd_ctl_tlv __user *_tlv)
|
struct snd_ctl_tlv __user *_tlv,
|
||||||
|
int op_flag)
|
||||||
{
|
{
|
||||||
|
struct snd_card *card = file->card;
|
||||||
struct snd_ctl_tlv tlv;
|
struct snd_ctl_tlv tlv;
|
||||||
struct snd_kcontrol *kctl;
|
struct snd_kcontrol *kctl;
|
||||||
|
struct snd_kcontrol_volatile *vd;
|
||||||
unsigned int len;
|
unsigned int len;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
@ -1086,17 +1137,41 @@ static int snd_ctl_tlv_read(struct snd_card *card,
|
|||||||
err = -ENOENT;
|
err = -ENOENT;
|
||||||
goto __kctl_end;
|
goto __kctl_end;
|
||||||
}
|
}
|
||||||
if (kctl->tlv == NULL) {
|
if (kctl->tlv.p == NULL) {
|
||||||
err = -ENXIO;
|
err = -ENXIO;
|
||||||
goto __kctl_end;
|
goto __kctl_end;
|
||||||
}
|
}
|
||||||
len = kctl->tlv[1] + 2 * sizeof(unsigned int);
|
vd = &kctl->vd[tlv.numid - kctl->id.numid];
|
||||||
|
if ((op_flag == 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) == 0) ||
|
||||||
|
(op_flag > 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) == 0) ||
|
||||||
|
(op_flag < 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND) == 0)) {
|
||||||
|
err = -ENXIO;
|
||||||
|
goto __kctl_end;
|
||||||
|
}
|
||||||
|
if (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) {
|
||||||
|
if (file && vd->owner != NULL && vd->owner != file) {
|
||||||
|
err = -EPERM;
|
||||||
|
goto __kctl_end;
|
||||||
|
}
|
||||||
|
err = kctl->tlv.c(kctl, op_flag, tlv.length, _tlv->tlv);
|
||||||
|
if (err > 0) {
|
||||||
|
up_read(&card->controls_rwsem);
|
||||||
|
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_TLV, &kctl->id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (op_flag) {
|
||||||
|
err = -ENXIO;
|
||||||
|
goto __kctl_end;
|
||||||
|
}
|
||||||
|
len = kctl->tlv.p[1] + 2 * sizeof(unsigned int);
|
||||||
if (tlv.length < len) {
|
if (tlv.length < len) {
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
goto __kctl_end;
|
goto __kctl_end;
|
||||||
}
|
}
|
||||||
if (copy_to_user(_tlv->tlv, kctl->tlv, len))
|
if (copy_to_user(_tlv->tlv, kctl->tlv.p, len))
|
||||||
err = -EFAULT;
|
err = -EFAULT;
|
||||||
|
}
|
||||||
__kctl_end:
|
__kctl_end:
|
||||||
up_read(&card->controls_rwsem);
|
up_read(&card->controls_rwsem);
|
||||||
return err;
|
return err;
|
||||||
@ -1141,7 +1216,11 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg
|
|||||||
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
|
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
|
||||||
return snd_ctl_subscribe_events(ctl, ip);
|
return snd_ctl_subscribe_events(ctl, ip);
|
||||||
case SNDRV_CTL_IOCTL_TLV_READ:
|
case SNDRV_CTL_IOCTL_TLV_READ:
|
||||||
return snd_ctl_tlv_read(card, argp);
|
return snd_ctl_tlv_ioctl(ctl, argp, 0);
|
||||||
|
case SNDRV_CTL_IOCTL_TLV_WRITE:
|
||||||
|
return snd_ctl_tlv_ioctl(ctl, argp, 1);
|
||||||
|
case SNDRV_CTL_IOCTL_TLV_COMMAND:
|
||||||
|
return snd_ctl_tlv_ioctl(ctl, argp, -1);
|
||||||
case SNDRV_CTL_IOCTL_POWER:
|
case SNDRV_CTL_IOCTL_POWER:
|
||||||
return -ENOPROTOOPT;
|
return -ENOPROTOOPT;
|
||||||
case SNDRV_CTL_IOCTL_POWER_STATE:
|
case SNDRV_CTL_IOCTL_POWER_STATE:
|
||||||
|
Loading…
Reference in New Issue
Block a user