diff options
Diffstat (limited to 'sound/virtio/virtio_kctl.c')
| -rw-r--r-- | sound/virtio/virtio_kctl.c | 477 | 
1 files changed, 477 insertions, 0 deletions
diff --git a/sound/virtio/virtio_kctl.c b/sound/virtio/virtio_kctl.c new file mode 100644 index 000000000000..7aa79c05b464 --- /dev/null +++ b/sound/virtio/virtio_kctl.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * virtio-snd: Virtio sound device + * Copyright (C) 2022 OpenSynergy GmbH + */ +#include <sound/control.h> +#include <linux/virtio_config.h> + +#include "virtio_card.h" + +/* Map for converting VirtIO types to ALSA types. */ +static const snd_ctl_elem_type_t g_v2a_type_map[] = { +	[VIRTIO_SND_CTL_TYPE_BOOLEAN] = SNDRV_CTL_ELEM_TYPE_BOOLEAN, +	[VIRTIO_SND_CTL_TYPE_INTEGER] = SNDRV_CTL_ELEM_TYPE_INTEGER, +	[VIRTIO_SND_CTL_TYPE_INTEGER64] = SNDRV_CTL_ELEM_TYPE_INTEGER64, +	[VIRTIO_SND_CTL_TYPE_ENUMERATED] = SNDRV_CTL_ELEM_TYPE_ENUMERATED, +	[VIRTIO_SND_CTL_TYPE_BYTES] = SNDRV_CTL_ELEM_TYPE_BYTES, +	[VIRTIO_SND_CTL_TYPE_IEC958] = SNDRV_CTL_ELEM_TYPE_IEC958 +}; + +/* Map for converting VirtIO access rights to ALSA access rights. */ +static const unsigned int g_v2a_access_map[] = { +	[VIRTIO_SND_CTL_ACCESS_READ] = SNDRV_CTL_ELEM_ACCESS_READ, +	[VIRTIO_SND_CTL_ACCESS_WRITE] = SNDRV_CTL_ELEM_ACCESS_WRITE, +	[VIRTIO_SND_CTL_ACCESS_VOLATILE] = SNDRV_CTL_ELEM_ACCESS_VOLATILE, +	[VIRTIO_SND_CTL_ACCESS_INACTIVE] = SNDRV_CTL_ELEM_ACCESS_INACTIVE, +	[VIRTIO_SND_CTL_ACCESS_TLV_READ] = SNDRV_CTL_ELEM_ACCESS_TLV_READ, +	[VIRTIO_SND_CTL_ACCESS_TLV_WRITE] = SNDRV_CTL_ELEM_ACCESS_TLV_WRITE, +	[VIRTIO_SND_CTL_ACCESS_TLV_COMMAND] = SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND +}; + +/* Map for converting VirtIO event masks to ALSA event masks. */ +static const unsigned int g_v2a_mask_map[] = { +	[VIRTIO_SND_CTL_EVT_MASK_VALUE] = SNDRV_CTL_EVENT_MASK_VALUE, +	[VIRTIO_SND_CTL_EVT_MASK_INFO] = SNDRV_CTL_EVENT_MASK_INFO, +	[VIRTIO_SND_CTL_EVT_MASK_TLV] = SNDRV_CTL_EVENT_MASK_TLV +}; + +/** + * virtsnd_kctl_info() - Returns information about the control. + * @kcontrol: ALSA control element. + * @uinfo: Element information. + * + * Context: Process context. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_kctl_info(struct snd_kcontrol *kcontrol, +			     struct snd_ctl_elem_info *uinfo) +{ +	struct virtio_snd *snd = kcontrol->private_data; +	struct virtio_kctl *kctl = &snd->kctls[kcontrol->private_value]; +	struct virtio_snd_ctl_info *kinfo = +		&snd->kctl_infos[kcontrol->private_value]; +	unsigned int i; + +	uinfo->type = g_v2a_type_map[le32_to_cpu(kinfo->type)]; +	uinfo->count = le32_to_cpu(kinfo->count); + +	switch (uinfo->type) { +	case SNDRV_CTL_ELEM_TYPE_INTEGER: +		uinfo->value.integer.min = +			le32_to_cpu(kinfo->value.integer.min); +		uinfo->value.integer.max = +			le32_to_cpu(kinfo->value.integer.max); +		uinfo->value.integer.step = +			le32_to_cpu(kinfo->value.integer.step); + +		break; +	case SNDRV_CTL_ELEM_TYPE_INTEGER64: +		uinfo->value.integer64.min = +			le64_to_cpu(kinfo->value.integer64.min); +		uinfo->value.integer64.max = +			le64_to_cpu(kinfo->value.integer64.max); +		uinfo->value.integer64.step = +			le64_to_cpu(kinfo->value.integer64.step); + +		break; +	case SNDRV_CTL_ELEM_TYPE_ENUMERATED: +		uinfo->value.enumerated.items = +			le32_to_cpu(kinfo->value.enumerated.items); +		i = uinfo->value.enumerated.item; +		if (i >= uinfo->value.enumerated.items) +			return -EINVAL; + +		strscpy(uinfo->value.enumerated.name, kctl->items[i].item, +			sizeof(uinfo->value.enumerated.name)); + +		break; +	} + +	return 0; +} + +/** + * virtsnd_kctl_get() - Read the value from the control. + * @kcontrol: ALSA control element. + * @uvalue: Element value. + * + * Context: Process context. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_kctl_get(struct snd_kcontrol *kcontrol, +			    struct snd_ctl_elem_value *uvalue) +{ +	struct virtio_snd *snd = kcontrol->private_data; +	struct virtio_snd_ctl_info *kinfo = +		&snd->kctl_infos[kcontrol->private_value]; +	unsigned int type = le32_to_cpu(kinfo->type); +	unsigned int count = le32_to_cpu(kinfo->count); +	struct virtio_snd_msg *msg; +	struct virtio_snd_ctl_hdr *hdr; +	struct virtio_snd_ctl_value *kvalue; +	size_t request_size = sizeof(*hdr); +	size_t response_size = sizeof(struct virtio_snd_hdr) + sizeof(*kvalue); +	unsigned int i; +	int rc; + +	msg = virtsnd_ctl_msg_alloc(request_size, response_size, GFP_KERNEL); +	if (!msg) +		return -ENOMEM; + +	virtsnd_ctl_msg_ref(msg); + +	hdr = virtsnd_ctl_msg_request(msg); +	hdr->hdr.code = cpu_to_le32(VIRTIO_SND_R_CTL_READ); +	hdr->control_id = cpu_to_le32(kcontrol->private_value); + +	rc = virtsnd_ctl_msg_send_sync(snd, msg); +	if (rc) +		goto on_failure; + +	kvalue = (void *)((u8 *)virtsnd_ctl_msg_response(msg) + +			  sizeof(struct virtio_snd_hdr)); + +	switch (type) { +	case VIRTIO_SND_CTL_TYPE_BOOLEAN: +	case VIRTIO_SND_CTL_TYPE_INTEGER: +		for (i = 0; i < count; ++i) +			uvalue->value.integer.value[i] = +				le32_to_cpu(kvalue->value.integer[i]); +		break; +	case VIRTIO_SND_CTL_TYPE_INTEGER64: +		for (i = 0; i < count; ++i) +			uvalue->value.integer64.value[i] = +				le64_to_cpu(kvalue->value.integer64[i]); +		break; +	case VIRTIO_SND_CTL_TYPE_ENUMERATED: +		for (i = 0; i < count; ++i) +			uvalue->value.enumerated.item[i] = +				le32_to_cpu(kvalue->value.enumerated[i]); +		break; +	case VIRTIO_SND_CTL_TYPE_BYTES: +		memcpy(uvalue->value.bytes.data, kvalue->value.bytes, count); +		break; +	case VIRTIO_SND_CTL_TYPE_IEC958: +		memcpy(&uvalue->value.iec958, &kvalue->value.iec958, +		       sizeof(uvalue->value.iec958)); +		break; +	} + +on_failure: +	virtsnd_ctl_msg_unref(msg); + +	return rc; +} + +/** + * virtsnd_kctl_put() - Write the value to the control. + * @kcontrol: ALSA control element. + * @uvalue: Element value. + * + * Context: Process context. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_kctl_put(struct snd_kcontrol *kcontrol, +			    struct snd_ctl_elem_value *uvalue) +{ +	struct virtio_snd *snd = kcontrol->private_data; +	struct virtio_snd_ctl_info *kinfo = +		&snd->kctl_infos[kcontrol->private_value]; +	unsigned int type = le32_to_cpu(kinfo->type); +	unsigned int count = le32_to_cpu(kinfo->count); +	struct virtio_snd_msg *msg; +	struct virtio_snd_ctl_hdr *hdr; +	struct virtio_snd_ctl_value *kvalue; +	size_t request_size = sizeof(*hdr) + sizeof(*kvalue); +	size_t response_size = sizeof(struct virtio_snd_hdr); +	unsigned int i; + +	msg = virtsnd_ctl_msg_alloc(request_size, response_size, GFP_KERNEL); +	if (!msg) +		return -ENOMEM; + +	hdr = virtsnd_ctl_msg_request(msg); +	hdr->hdr.code = cpu_to_le32(VIRTIO_SND_R_CTL_WRITE); +	hdr->control_id = cpu_to_le32(kcontrol->private_value); + +	kvalue = (void *)((u8 *)hdr + sizeof(*hdr)); + +	switch (type) { +	case VIRTIO_SND_CTL_TYPE_BOOLEAN: +	case VIRTIO_SND_CTL_TYPE_INTEGER: +		for (i = 0; i < count; ++i) +			kvalue->value.integer[i] = +				cpu_to_le32(uvalue->value.integer.value[i]); +		break; +	case VIRTIO_SND_CTL_TYPE_INTEGER64: +		for (i = 0; i < count; ++i) +			kvalue->value.integer64[i] = +				cpu_to_le64(uvalue->value.integer64.value[i]); +		break; +	case VIRTIO_SND_CTL_TYPE_ENUMERATED: +		for (i = 0; i < count; ++i) +			kvalue->value.enumerated[i] = +				cpu_to_le32(uvalue->value.enumerated.item[i]); +		break; +	case VIRTIO_SND_CTL_TYPE_BYTES: +		memcpy(kvalue->value.bytes, uvalue->value.bytes.data, count); +		break; +	case VIRTIO_SND_CTL_TYPE_IEC958: +		memcpy(&kvalue->value.iec958, &uvalue->value.iec958, +		       sizeof(kvalue->value.iec958)); +		break; +	} + +	return virtsnd_ctl_msg_send_sync(snd, msg); +} + +/** + * virtsnd_kctl_tlv_op() - Perform an operation on the control's metadata. + * @kcontrol: ALSA control element. + * @op_flag: Operation code (SNDRV_CTL_TLV_OP_XXX). + * @size: Size of the TLV data in bytes. + * @utlv: TLV data. + * + * Context: Process context. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_kctl_tlv_op(struct snd_kcontrol *kcontrol, int op_flag, +			       unsigned int size, unsigned int __user *utlv) +{ +	struct virtio_snd *snd = kcontrol->private_data; +	struct virtio_snd_msg *msg; +	struct virtio_snd_ctl_hdr *hdr; +	unsigned int *tlv; +	struct scatterlist sg; +	int rc; + +	msg = virtsnd_ctl_msg_alloc(sizeof(*hdr), sizeof(struct virtio_snd_hdr), +				    GFP_KERNEL); +	if (!msg) +		return -ENOMEM; + +	tlv = kzalloc(size, GFP_KERNEL); +	if (!tlv) { +		rc = -ENOMEM; +		goto on_msg_unref; +	} + +	sg_init_one(&sg, tlv, size); + +	hdr = virtsnd_ctl_msg_request(msg); +	hdr->control_id = cpu_to_le32(kcontrol->private_value); + +	switch (op_flag) { +	case SNDRV_CTL_TLV_OP_READ: +		hdr->hdr.code = cpu_to_le32(VIRTIO_SND_R_CTL_TLV_READ); + +		rc = virtsnd_ctl_msg_send(snd, msg, NULL, &sg, false); +		if (!rc) { +			if (copy_to_user(utlv, tlv, size)) +				rc = -EFAULT; +		} + +		break; +	case SNDRV_CTL_TLV_OP_WRITE: +	case SNDRV_CTL_TLV_OP_CMD: +		if (op_flag == SNDRV_CTL_TLV_OP_WRITE) +			hdr->hdr.code = cpu_to_le32(VIRTIO_SND_R_CTL_TLV_WRITE); +		else +			hdr->hdr.code = +				cpu_to_le32(VIRTIO_SND_R_CTL_TLV_COMMAND); + +		if (copy_from_user(tlv, utlv, size)) { +			rc = -EFAULT; +			goto on_msg_unref; +		} else { +			rc = virtsnd_ctl_msg_send(snd, msg, &sg, NULL, false); +		} + +		break; +	default: +		rc = -EINVAL; +		/* We never get here - we listed all values for op_flag */ +		WARN_ON(1); +		goto on_msg_unref; +	} +	kfree(tlv); +	return rc; + +on_msg_unref: +	virtsnd_ctl_msg_unref(msg); +	kfree(tlv); + +	return rc; +} + +/** + * virtsnd_kctl_get_enum_items() - Query items for the ENUMERATED element type. + * @snd: VirtIO sound device. + * @cid: Control element ID. + * + * This function is called during initial device initialization. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +static int virtsnd_kctl_get_enum_items(struct virtio_snd *snd, unsigned int cid) +{ +	struct virtio_device *vdev = snd->vdev; +	struct virtio_snd_ctl_info *kinfo = &snd->kctl_infos[cid]; +	struct virtio_kctl *kctl = &snd->kctls[cid]; +	struct virtio_snd_msg *msg; +	struct virtio_snd_ctl_hdr *hdr; +	unsigned int n = le32_to_cpu(kinfo->value.enumerated.items); +	struct scatterlist sg; + +	msg = virtsnd_ctl_msg_alloc(sizeof(*hdr), +				    sizeof(struct virtio_snd_hdr), GFP_KERNEL); +	if (!msg) +		return -ENOMEM; + +	kctl->items = devm_kcalloc(&vdev->dev, n, sizeof(*kctl->items), +				   GFP_KERNEL); +	if (!kctl->items) { +		virtsnd_ctl_msg_unref(msg); +		return -ENOMEM; +	} + +	sg_init_one(&sg, kctl->items, n * sizeof(*kctl->items)); + +	hdr = virtsnd_ctl_msg_request(msg); +	hdr->hdr.code = cpu_to_le32(VIRTIO_SND_R_CTL_ENUM_ITEMS); +	hdr->control_id = cpu_to_le32(cid); + +	return virtsnd_ctl_msg_send(snd, msg, NULL, &sg, false); +} + +/** + * virtsnd_kctl_parse_cfg() - Parse the control element configuration. + * @snd: VirtIO sound device. + * + * This function is called during initial device initialization. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +int virtsnd_kctl_parse_cfg(struct virtio_snd *snd) +{ +	struct virtio_device *vdev = snd->vdev; +	u32 i; +	int rc; + +	virtio_cread_le(vdev, struct virtio_snd_config, controls, +			&snd->nkctls); +	if (!snd->nkctls) +		return 0; + +	snd->kctl_infos = devm_kcalloc(&vdev->dev, snd->nkctls, +				       sizeof(*snd->kctl_infos), GFP_KERNEL); +	if (!snd->kctl_infos) +		return -ENOMEM; + +	snd->kctls = devm_kcalloc(&vdev->dev, snd->nkctls, sizeof(*snd->kctls), +				  GFP_KERNEL); +	if (!snd->kctls) +		return -ENOMEM; + +	rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_CTL_INFO, 0, snd->nkctls, +				    sizeof(*snd->kctl_infos), snd->kctl_infos); +	if (rc) +		return rc; + +	for (i = 0; i < snd->nkctls; ++i) { +		struct virtio_snd_ctl_info *kinfo = &snd->kctl_infos[i]; +		unsigned int type = le32_to_cpu(kinfo->type); + +		if (type == VIRTIO_SND_CTL_TYPE_ENUMERATED) { +			rc = virtsnd_kctl_get_enum_items(snd, i); +			if (rc) +				return rc; +		} +	} + +	return 0; +} + +/** + * virtsnd_kctl_build_devs() - Build ALSA control elements. + * @snd: VirtIO sound device. + * + * Context: Any context that permits to sleep. + * Return: 0 on success, -errno on failure. + */ +int virtsnd_kctl_build_devs(struct virtio_snd *snd) +{ +	unsigned int cid; + +	for (cid = 0; cid < snd->nkctls; ++cid) { +		struct virtio_snd_ctl_info *kinfo = &snd->kctl_infos[cid]; +		struct virtio_kctl *kctl = &snd->kctls[cid]; +		struct snd_kcontrol_new kctl_new; +		unsigned int i; +		int rc; + +		memset(&kctl_new, 0, sizeof(kctl_new)); + +		kctl_new.iface = SNDRV_CTL_ELEM_IFACE_MIXER; +		kctl_new.name = kinfo->name; +		kctl_new.index = le32_to_cpu(kinfo->index); + +		for (i = 0; i < ARRAY_SIZE(g_v2a_access_map); ++i) +			if (le32_to_cpu(kinfo->access) & (1 << i)) +				kctl_new.access |= g_v2a_access_map[i]; + +		if (kctl_new.access & (SNDRV_CTL_ELEM_ACCESS_TLV_READ | +				       SNDRV_CTL_ELEM_ACCESS_TLV_WRITE | +				       SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND)) { +			kctl_new.access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; +			kctl_new.tlv.c = virtsnd_kctl_tlv_op; +		} + +		kctl_new.info = virtsnd_kctl_info; +		kctl_new.get = virtsnd_kctl_get; +		kctl_new.put = virtsnd_kctl_put; +		kctl_new.private_value = cid; + +		kctl->kctl = snd_ctl_new1(&kctl_new, snd); +		if (!kctl->kctl) +			return -ENOMEM; + +		rc = snd_ctl_add(snd->card, kctl->kctl); +		if (rc) +			return rc; +	} + +	return 0; +} + +/** + * virtsnd_kctl_event() - Handle the control element event notification. + * @snd: VirtIO sound device. + * @event: VirtIO sound event. + * + * Context: Interrupt context. + */ +void virtsnd_kctl_event(struct virtio_snd *snd, struct virtio_snd_event *event) +{ +	struct virtio_snd_ctl_event *kevent = +		(struct virtio_snd_ctl_event *)event; +	struct virtio_kctl *kctl; +	unsigned int cid = le16_to_cpu(kevent->control_id); +	unsigned int mask = 0; +	unsigned int i; + +	if (cid >= snd->nkctls) +		return; + +	for (i = 0; i < ARRAY_SIZE(g_v2a_mask_map); ++i) +		if (le16_to_cpu(kevent->mask) & (1 << i)) +			mask |= g_v2a_mask_map[i]; + + +	kctl = &snd->kctls[cid]; + +	snd_ctl_notify(snd->card, mask, &kctl->kctl->id); +}  |