[PATCH 0/1] ALSA: virtio: add support for audio controls
Implementation of support for audio controls in accordance with the proposed extension of the virtio sound device specification [1].
Design of virtual audio controls is based on and derived from ALSA audio controls. It allows the driver to perform all standard operations, such as reading and writing audio control value, as well as working with metadata (represented in the TLV form).
The driver part was tested on top of the Linux 5.10 kernel.
As a device part was used OpenSynergy proprietary implementation.
[1] https://lists.oasis-open.org/archives/virtio-comment/202104/msg00013.html
Anton Yakovlev (1): ALSA: virtio: add support for audio controls
include/uapi/linux/virtio_snd.h | 154 +++++++++++ sound/virtio/Makefile | 1 + sound/virtio/virtio_card.c | 21 ++ sound/virtio/virtio_card.h | 22 ++ sound/virtio/virtio_kctl.c | 464 ++++++++++++++++++++++++++++++++ 5 files changed, 662 insertions(+) create mode 100644 sound/virtio/virtio_kctl.c
Implementation of support for audio controls in accordance with the proposed extension of the virtio sound device specification [1].
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 --- include/uapi/linux/virtio_snd.h | 154 +++++++++++ sound/virtio/Makefile | 1 + sound/virtio/virtio_card.c | 21 ++ sound/virtio/virtio_card.h | 22 ++ sound/virtio/virtio_kctl.c | 464 ++++++++++++++++++++++++++++++++ 5 files changed, 662 insertions(+) create mode 100644 sound/virtio/virtio_kctl.c
diff --git a/include/uapi/linux/virtio_snd.h b/include/uapi/linux/virtio_snd.h index dfe49547a7b0..5f4100c2cf04 100644 --- a/include/uapi/linux/virtio_snd.h +++ b/include/uapi/linux/virtio_snd.h @@ -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 */ diff --git a/sound/virtio/Makefile b/sound/virtio/Makefile index 2742bddb8874..a839f8c8b5e6 100644 --- a/sound/virtio/Makefile +++ b/sound/virtio/Makefile @@ -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 diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index e2847c040f75..61df3476cf70 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -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; } }
@@ -235,6 +238,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) @@ -253,6 +262,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); }
@@ -419,10 +434,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, diff --git a/sound/virtio/virtio_card.h b/sound/virtio/virtio_card.h index 86ef3941895e..3ceee4e416fc 100644 --- a/sound/virtio/virtio_card.h +++ b/sound/virtio/virtio_card.h @@ -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 index 000000000000..16c48abe0ca1 --- /dev/null +++ b/sound/virtio/virtio_kctl.c @@ -0,0 +1,464 @@ +// 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: + i = uinfo->value.enumerated.item; + if (i >= le32_to_cpu(kinfo->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); +}
participants (1)
-
Anton Yakovlev