| // 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); |
| } |