ALSA: virtio: add support for audio controls
authorAnton Yakovlev <anton.yakovlev@opensynergy.com>
Mon, 15 Jan 2024 13:36:54 +0000 (14:36 +0100)
committerTakashi Iwai <tiwai@suse.de>
Fri, 9 Feb 2024 13:01:15 +0000 (14:01 +0100)
Implementation of support for audio controls in accordance with the
extension of the virtio sound device specification [1] planned for
virtio-v1.3-cs01.

The device can announce the VIRTIO_SND_F_CTLS feature. If the feature is
negotiated, then an additional field appears in the configuration space:

  struct virtio_snd_config {
    ...
    /* number of available control elements */
    __le32 controls;
  };

The driver can send the following requests to manage audio controls:

  enum {
    ...
    /* control element request types */
    VIRTIO_SND_R_CTL_INFO = 0x0300,
    VIRTIO_SND_R_CTL_ENUM_ITEMS,
    VIRTIO_SND_R_CTL_READ,
    VIRTIO_SND_R_CTL_WRITE,
    VIRTIO_SND_R_CTL_TLV_READ,
    VIRTIO_SND_R_CTL_TLV_WRITE,
    VIRTIO_SND_R_CTL_TLV_COMMAND,
    ...
  };

And the device can send the following audio control event notification:

  enum {
    ...
    /* control element event types */
    VIRTIO_SND_EVT_CTL_NOTIFY = 0x1200,
    ...
  };

See additional details in [1].

[1] https://lists.oasis-open.org/archives/virtio-comment/202104/msg00013.html

Signed-off-by: Anton Yakovlev <anton.yakovlev@opensynergy.com>
Signed-off-by: Aiswarya Cyriac <aiswarya.cyriac@opensynergy.com>
Link: https://lore.kernel.org/r/20240115133654.576068-2-aiswarya.cyriac@opensynergy.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
include/uapi/linux/virtio_snd.h
sound/virtio/Makefile
sound/virtio/virtio_card.c
sound/virtio/virtio_card.h
sound/virtio/virtio_kctl.c [new file with mode: 0644]

index dfe49547a7b04d6aa7e9cb474a0f103b2cd2b043..5f4100c2cf04cb44494fec115b70095f530b42c2 100644 (file)
@@ -7,6 +7,14 @@
 
 #include <linux/virtio_types.h>
 
+/*******************************************************************************
+ * FEATURE BITS
+ */
+enum {
+       /* device supports control elements */
+       VIRTIO_SND_F_CTLS = 0
+};
+
 /*******************************************************************************
  * CONFIGURATION SPACE
  */
@@ -17,6 +25,8 @@ struct virtio_snd_config {
        __le32 streams;
        /* # of available channel maps */
        __le32 chmaps;
+       /* # of available control elements */
+       __le32 controls;
 };
 
 enum {
@@ -55,6 +65,15 @@ enum {
        /* channel map control request types */
        VIRTIO_SND_R_CHMAP_INFO = 0x0200,
 
+       /* control element request types */
+       VIRTIO_SND_R_CTL_INFO = 0x0300,
+       VIRTIO_SND_R_CTL_ENUM_ITEMS,
+       VIRTIO_SND_R_CTL_READ,
+       VIRTIO_SND_R_CTL_WRITE,
+       VIRTIO_SND_R_CTL_TLV_READ,
+       VIRTIO_SND_R_CTL_TLV_WRITE,
+       VIRTIO_SND_R_CTL_TLV_COMMAND,
+
        /* jack event types */
        VIRTIO_SND_EVT_JACK_CONNECTED = 0x1000,
        VIRTIO_SND_EVT_JACK_DISCONNECTED,
@@ -63,6 +82,9 @@ enum {
        VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED = 0x1100,
        VIRTIO_SND_EVT_PCM_XRUN,
 
+       /* control element event types */
+       VIRTIO_SND_EVT_CTL_NOTIFY = 0x1200,
+
        /* common status codes */
        VIRTIO_SND_S_OK = 0x8000,
        VIRTIO_SND_S_BAD_MSG,
@@ -331,4 +353,136 @@ struct virtio_snd_chmap_info {
        __u8 positions[VIRTIO_SND_CHMAP_MAX_SIZE];
 };
 
+/*******************************************************************************
+ * CONTROL ELEMENTS MESSAGES
+ */
+struct virtio_snd_ctl_hdr {
+       /* VIRTIO_SND_R_CTL_XXX */
+       struct virtio_snd_hdr hdr;
+       /* 0 ... virtio_snd_config::controls - 1 */
+       __le32 control_id;
+};
+
+/* supported roles for control elements */
+enum {
+       VIRTIO_SND_CTL_ROLE_UNDEFINED = 0,
+       VIRTIO_SND_CTL_ROLE_VOLUME,
+       VIRTIO_SND_CTL_ROLE_MUTE,
+       VIRTIO_SND_CTL_ROLE_GAIN
+};
+
+/* supported value types for control elements */
+enum {
+       VIRTIO_SND_CTL_TYPE_BOOLEAN = 0,
+       VIRTIO_SND_CTL_TYPE_INTEGER,
+       VIRTIO_SND_CTL_TYPE_INTEGER64,
+       VIRTIO_SND_CTL_TYPE_ENUMERATED,
+       VIRTIO_SND_CTL_TYPE_BYTES,
+       VIRTIO_SND_CTL_TYPE_IEC958
+};
+
+/* supported access rights for control elements */
+enum {
+       VIRTIO_SND_CTL_ACCESS_READ = 0,
+       VIRTIO_SND_CTL_ACCESS_WRITE,
+       VIRTIO_SND_CTL_ACCESS_VOLATILE,
+       VIRTIO_SND_CTL_ACCESS_INACTIVE,
+       VIRTIO_SND_CTL_ACCESS_TLV_READ,
+       VIRTIO_SND_CTL_ACCESS_TLV_WRITE,
+       VIRTIO_SND_CTL_ACCESS_TLV_COMMAND
+};
+
+struct virtio_snd_ctl_info {
+       /* common header */
+       struct virtio_snd_info hdr;
+       /* element role (VIRTIO_SND_CTL_ROLE_XXX) */
+       __le32 role;
+       /* element value type (VIRTIO_SND_CTL_TYPE_XXX) */
+       __le32 type;
+       /* element access right bit map (1 << VIRTIO_SND_CTL_ACCESS_XXX) */
+       __le32 access;
+       /* # of members in the element value */
+       __le32 count;
+       /* index for an element with a non-unique name */
+       __le32 index;
+       /* name identifier string for the element */
+       __u8 name[44];
+       /* additional information about the element's value */
+       union {
+               /* VIRTIO_SND_CTL_TYPE_INTEGER */
+               struct {
+                       /* minimum supported value */
+                       __le32 min;
+                       /* maximum supported value */
+                       __le32 max;
+                       /* fixed step size for value (0 = variable size) */
+                       __le32 step;
+               } integer;
+               /* VIRTIO_SND_CTL_TYPE_INTEGER64 */
+               struct {
+                       /* minimum supported value */
+                       __le64 min;
+                       /* maximum supported value */
+                       __le64 max;
+                       /* fixed step size for value (0 = variable size) */
+                       __le64 step;
+               } integer64;
+               /* VIRTIO_SND_CTL_TYPE_ENUMERATED */
+               struct {
+                       /* # of options supported for value */
+                       __le32 items;
+               } enumerated;
+       } value;
+};
+
+struct virtio_snd_ctl_enum_item {
+       /* option name */
+       __u8 item[64];
+};
+
+struct virtio_snd_ctl_iec958 {
+       /* AES/IEC958 channel status bits */
+       __u8 status[24];
+       /* AES/IEC958 subcode bits */
+       __u8 subcode[147];
+       /* nothing */
+       __u8 pad;
+       /* AES/IEC958 subframe bits */
+       __u8 dig_subframe[4];
+};
+
+struct virtio_snd_ctl_value {
+       union {
+               /* VIRTIO_SND_CTL_TYPE_BOOLEAN|INTEGER value */
+               __le32 integer[128];
+               /* VIRTIO_SND_CTL_TYPE_INTEGER64 value */
+               __le64 integer64[64];
+               /* VIRTIO_SND_CTL_TYPE_ENUMERATED value (option indexes) */
+               __le32 enumerated[128];
+               /* VIRTIO_SND_CTL_TYPE_BYTES value */
+               __u8 bytes[512];
+               /* VIRTIO_SND_CTL_TYPE_IEC958 value */
+               struct virtio_snd_ctl_iec958 iec958;
+       } value;
+};
+
+/* supported event reason types */
+enum {
+       /* element's value has changed */
+       VIRTIO_SND_CTL_EVT_MASK_VALUE = 0,
+       /* element's information has changed */
+       VIRTIO_SND_CTL_EVT_MASK_INFO,
+       /* element's metadata has changed */
+       VIRTIO_SND_CTL_EVT_MASK_TLV
+};
+
+struct virtio_snd_ctl_event {
+       /* VIRTIO_SND_EVT_CTL_NOTIFY */
+       struct virtio_snd_hdr hdr;
+       /* 0 ... virtio_snd_config::controls - 1 */
+       __le16 control_id;
+       /* event reason bit map (1 << VIRTIO_SND_CTL_EVT_MASK_XXX) */
+       __le16 mask;
+};
+
 #endif /* VIRTIO_SND_IF_H */
index 2742bddb887414d7bb97f0efd32325e02b3513f9..a839f8c8b5e6478c9f7b6ec3c8b60e6be4750ecb 100644 (file)
@@ -7,6 +7,7 @@ virtio_snd-objs := \
        virtio_chmap.o \
        virtio_ctl_msg.o \
        virtio_jack.o \
+       virtio_kctl.o \
        virtio_pcm.o \
        virtio_pcm_msg.o \
        virtio_pcm_ops.o
index b158c3cb8e5f5fce75e22306c7707935465bc57f..2da20c6252477cabe4c2dd3adbc29cc422500ee5 100644 (file)
@@ -64,6 +64,9 @@ static void virtsnd_event_dispatch(struct virtio_snd *snd,
        case VIRTIO_SND_EVT_PCM_XRUN:
                virtsnd_pcm_event(snd, event);
                break;
+       case VIRTIO_SND_EVT_CTL_NOTIFY:
+               virtsnd_kctl_event(snd, event);
+               break;
        }
 }
 
@@ -233,6 +236,12 @@ static int virtsnd_build_devs(struct virtio_snd *snd)
        if (rc)
                return rc;
 
+       if (virtio_has_feature(vdev, VIRTIO_SND_F_CTLS)) {
+               rc = virtsnd_kctl_parse_cfg(snd);
+               if (rc)
+                       return rc;
+       }
+
        if (snd->njacks) {
                rc = virtsnd_jack_build_devs(snd);
                if (rc)
@@ -251,6 +260,12 @@ static int virtsnd_build_devs(struct virtio_snd *snd)
                        return rc;
        }
 
+       if (snd->nkctls) {
+               rc = virtsnd_kctl_build_devs(snd);
+               if (rc)
+                       return rc;
+       }
+
        return snd_card_register(snd->card);
 }
 
@@ -417,10 +432,16 @@ static const struct virtio_device_id id_table[] = {
        { 0 },
 };
 
+static unsigned int features[] = {
+       VIRTIO_SND_F_CTLS
+};
+
 static struct virtio_driver virtsnd_driver = {
        .driver.name = KBUILD_MODNAME,
        .driver.owner = THIS_MODULE,
        .id_table = id_table,
+       .feature_table = features,
+       .feature_table_size = ARRAY_SIZE(features),
        .validate = virtsnd_validate,
        .probe = virtsnd_probe,
        .remove = virtsnd_remove,
index 86ef3941895ed25c9bfa1d46267bef99f2ad7101..3ceee4e416fc7be8c016064bfe08f26f8fe86fb8 100644 (file)
@@ -31,6 +31,16 @@ struct virtio_snd_queue {
        struct virtqueue *vqueue;
 };
 
+/**
+ * struct virtio_kctl - VirtIO control element.
+ * @kctl: ALSA control element.
+ * @items: Items for the ENUMERATED element type.
+ */
+struct virtio_kctl {
+       struct snd_kcontrol *kctl;
+       struct virtio_snd_ctl_enum_item *items;
+};
+
 /**
  * struct virtio_snd - VirtIO sound card device.
  * @vdev: Underlying virtio device.
@@ -45,6 +55,9 @@ struct virtio_snd_queue {
  * @nsubstreams: Number of PCM substreams.
  * @chmaps: VirtIO channel maps.
  * @nchmaps: Number of channel maps.
+ * @kctl_infos: VirtIO control element information.
+ * @kctls: VirtIO control elements.
+ * @nkctls: Number of control elements.
  */
 struct virtio_snd {
        struct virtio_device *vdev;
@@ -59,6 +72,9 @@ struct virtio_snd {
        u32 nsubstreams;
        struct virtio_snd_chmap_info *chmaps;
        u32 nchmaps;
+       struct virtio_snd_ctl_info *kctl_infos;
+       struct virtio_kctl *kctls;
+       u32 nkctls;
 };
 
 /* Message completion timeout in milliseconds (module parameter). */
@@ -108,4 +124,10 @@ int virtsnd_chmap_parse_cfg(struct virtio_snd *snd);
 
 int virtsnd_chmap_build_devs(struct virtio_snd *snd);
 
+int virtsnd_kctl_parse_cfg(struct virtio_snd *snd);
+
+int virtsnd_kctl_build_devs(struct virtio_snd *snd);
+
+void virtsnd_kctl_event(struct virtio_snd *snd, struct virtio_snd_event *event);
+
 #endif /* VIRTIO_SND_CARD_H */
diff --git a/sound/virtio/virtio_kctl.c b/sound/virtio/virtio_kctl.c
new file mode 100644 (file)
index 0000000..0c6ac74
--- /dev/null
@@ -0,0 +1,466 @@
+// 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) {
+               virtsnd_ctl_msg_unref(msg);
+               return -ENOMEM;
+       }
+
+       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;
+               else
+                       rc = virtsnd_ctl_msg_send(snd, msg, &sg, NULL, false);
+
+               break;
+       }
+
+       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);
+}