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