Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8aa9b586 authored by Jaroslav Kysela's avatar Jaroslav Kysela
Browse files

[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: default avatarJaroslav Kysela <perex@suse.cz>
parent 6bbe13ec
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -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_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_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_LOCK		(1<<9)	/* write lock */
#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_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 */
@@ -838,6 +843,8 @@ enum {
	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_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_INFO = _IOR('U', 0x21, struct snd_hwdep_info),
	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_INFO	(1<<1)	/* element info was changed */
#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 */

struct snd_ctl_event {
+14 −2
Original line number Diff line number Diff line
@@ -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_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_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 {
	snd_ctl_elem_iface_t iface;	/* interface identifier */
@@ -42,7 +47,10 @@ struct snd_kcontrol_new {
	snd_kcontrol_info_t *info;
	snd_kcontrol_get_t *get;
	snd_kcontrol_put_t *put;
	unsigned int *tlv;
	union {
		snd_kcontrol_tlv_rw_t *c;
		unsigned int *p;
	} tlv;
	unsigned long private_value;
};

@@ -59,7 +67,11 @@ struct snd_kcontrol {
	snd_kcontrol_info_t *info;
	snd_kcontrol_get_t *get;
	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;
	void *private_data;
	void (*private_free)(struct snd_kcontrol *kcontrol);
+109 −30
Original line number Diff line number Diff line
@@ -236,12 +236,16 @@ struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
	kctl.id.index = ncontrol->index;
	kctl.count = ncontrol->count ? ncontrol->count : 1;
	access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
		 (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE|
		 		      SNDRV_CTL_ELEM_ACCESS_DINDIRECT|SNDRV_CTL_ELEM_ACCESS_INDIRECT));
		 (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|
				      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.get = ncontrol->get;
	kctl.put = ncontrol->put;
	kctl.tlv = ncontrol->tlv;
	kctl.tlv.p = ncontrol->tlv.p;
	kctl.private_value = ncontrol->private_value;
	kctl.private_data = private_data;
	return snd_ctl_new(&kctl, access);
@@ -883,6 +887,8 @@ struct user_element {
	struct snd_ctl_elem_info info;
	void *elem_data;		/* element data */
	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) */
	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;
}

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)
{
	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,
@@ -938,7 +981,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
		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_INACTIVE|
				 SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE));
	info->id.numid = 0;
	memset(&kctl, 0, sizeof(kctl));
	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;
	if (access & SNDRV_CTL_ELEM_ACCESS_WRITE)
		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) {
	case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
		private_size = sizeof(char);
@@ -1068,11 +1116,14 @@ static int snd_ctl_subscribe_events(struct snd_ctl_file *file, int __user *ptr)
	return 0;
}

static int snd_ctl_tlv_read(struct snd_card *card,
                            struct snd_ctl_tlv __user *_tlv)
static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,
                             struct snd_ctl_tlv __user *_tlv,
                             int op_flag)
{
	struct snd_card *card = file->card;
	struct snd_ctl_tlv tlv;
	struct snd_kcontrol *kctl;
	struct snd_kcontrol_volatile *vd;
	unsigned int len;
	int err = 0;

@@ -1086,17 +1137,41 @@ static int snd_ctl_tlv_read(struct snd_card *card,
		err = -ENOENT;
		goto __kctl_end;
	}
        if (kctl->tlv == NULL) {
	if (kctl->tlv.p == NULL) {
		err = -ENXIO;
		goto __kctl_end;
	}
	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;
	}
        len = kctl->tlv[1] + 2 * sizeof(unsigned int);
	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) {
			err = -ENOMEM;
			goto __kctl_end;
		}
        if (copy_to_user(_tlv->tlv, kctl->tlv, len))
		if (copy_to_user(_tlv->tlv, kctl->tlv.p, len))
			err = -EFAULT;
	}
      __kctl_end:
	up_read(&card->controls_rwsem);
	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:
		return snd_ctl_subscribe_events(ctl, ip);
	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:
		return -ENOPROTOOPT;
	case SNDRV_CTL_IOCTL_POWER_STATE: