| // 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 types to maximum value counts. */ | |
| static const unsigned int g_v2a_count_map[] = { | |
| [VIRTIO_SND_CTL_TYPE_BOOLEAN] = | |
| ARRAY_SIZE(((struct virtio_snd_ctl_value *)0)->value.integer), | |
| [VIRTIO_SND_CTL_TYPE_INTEGER] = | |
| ARRAY_SIZE(((struct virtio_snd_ctl_value *)0)->value.integer), | |
| [VIRTIO_SND_CTL_TYPE_INTEGER64] = | |
| ARRAY_SIZE(((struct virtio_snd_ctl_value *)0)->value.integer64), | |
| [VIRTIO_SND_CTL_TYPE_ENUMERATED] = | |
| ARRAY_SIZE(((struct virtio_snd_ctl_value *)0)->value.enumerated), | |
| [VIRTIO_SND_CTL_TYPE_BYTES] = | |
| ARRAY_SIZE(((struct virtio_snd_ctl_value *)0)->value.bytes), | |
| [VIRTIO_SND_CTL_TYPE_IEC958] = 1 | |
| }; | |
| /* 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 | |
| }; | |
| static int virtsnd_kctl_validate_info(struct virtio_snd *snd, u32 cid, | |
| struct virtio_snd_ctl_info *kinfo) | |
| { | |
| struct virtio_device *vdev = snd->vdev; | |
| unsigned int type = le32_to_cpu(kinfo->type); | |
| unsigned int count = le32_to_cpu(kinfo->count); | |
| if (type >= ARRAY_SIZE(g_v2a_type_map)) { | |
| dev_err(&vdev->dev, "control #%u: unknown type %u\n", | |
| cid, type); | |
| return -EINVAL; | |
| } | |
| if (count > g_v2a_count_map[type] || | |
| (type == VIRTIO_SND_CTL_TYPE_IEC958 && count != 1)) { | |
| dev_err(&vdev->dev, "control #%u: invalid count %u for type %u\n", | |
| cid, count, type); | |
| return -EINVAL; | |
| } | |
| if (type == VIRTIO_SND_CTL_TYPE_ENUMERATED && | |
| !le32_to_cpu(kinfo->value.enumerated.items)) { | |
| dev_err(&vdev->dev, | |
| "control #%u: no items for enumerated control\n", | |
| cid); | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * 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 = snd_kcontrol_chip(kcontrol); | |
| 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 = snd_kcontrol_chip(kcontrol); | |
| 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 = snd_kcontrol_chip(kcontrol); | |
| 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 = snd_kcontrol_chip(kcontrol); | |
| 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); | |
| rc = virtsnd_kctl_validate_info(snd, i, kinfo); | |
| if (rc) | |
| return rc; | |
| 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); | |
| } |