// SPDX-License-Identifier: GPL-2.0+ /* * virtio-snd: Virtio sound device * Copyright (C) 2022 OpenSynergy GmbH */ #include #include #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); }