ALSA: virtio: add support for audio controls
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>
This commit is contained in:
parent
a097812310
commit
d6568e3de4
@ -7,6 +7,14 @@
|
|||||||
|
|
||||||
#include <linux/virtio_types.h>
|
#include <linux/virtio_types.h>
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* FEATURE BITS
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
/* device supports control elements */
|
||||||
|
VIRTIO_SND_F_CTLS = 0
|
||||||
|
};
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* CONFIGURATION SPACE
|
* CONFIGURATION SPACE
|
||||||
*/
|
*/
|
||||||
@ -17,6 +25,8 @@ struct virtio_snd_config {
|
|||||||
__le32 streams;
|
__le32 streams;
|
||||||
/* # of available channel maps */
|
/* # of available channel maps */
|
||||||
__le32 chmaps;
|
__le32 chmaps;
|
||||||
|
/* # of available control elements */
|
||||||
|
__le32 controls;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -55,6 +65,15 @@ enum {
|
|||||||
/* channel map control request types */
|
/* channel map control request types */
|
||||||
VIRTIO_SND_R_CHMAP_INFO = 0x0200,
|
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 */
|
/* jack event types */
|
||||||
VIRTIO_SND_EVT_JACK_CONNECTED = 0x1000,
|
VIRTIO_SND_EVT_JACK_CONNECTED = 0x1000,
|
||||||
VIRTIO_SND_EVT_JACK_DISCONNECTED,
|
VIRTIO_SND_EVT_JACK_DISCONNECTED,
|
||||||
@ -63,6 +82,9 @@ enum {
|
|||||||
VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED = 0x1100,
|
VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED = 0x1100,
|
||||||
VIRTIO_SND_EVT_PCM_XRUN,
|
VIRTIO_SND_EVT_PCM_XRUN,
|
||||||
|
|
||||||
|
/* control element event types */
|
||||||
|
VIRTIO_SND_EVT_CTL_NOTIFY = 0x1200,
|
||||||
|
|
||||||
/* common status codes */
|
/* common status codes */
|
||||||
VIRTIO_SND_S_OK = 0x8000,
|
VIRTIO_SND_S_OK = 0x8000,
|
||||||
VIRTIO_SND_S_BAD_MSG,
|
VIRTIO_SND_S_BAD_MSG,
|
||||||
@ -331,4 +353,136 @@ struct virtio_snd_chmap_info {
|
|||||||
__u8 positions[VIRTIO_SND_CHMAP_MAX_SIZE];
|
__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 */
|
#endif /* VIRTIO_SND_IF_H */
|
||||||
|
@ -7,6 +7,7 @@ virtio_snd-objs := \
|
|||||||
virtio_chmap.o \
|
virtio_chmap.o \
|
||||||
virtio_ctl_msg.o \
|
virtio_ctl_msg.o \
|
||||||
virtio_jack.o \
|
virtio_jack.o \
|
||||||
|
virtio_kctl.o \
|
||||||
virtio_pcm.o \
|
virtio_pcm.o \
|
||||||
virtio_pcm_msg.o \
|
virtio_pcm_msg.o \
|
||||||
virtio_pcm_ops.o
|
virtio_pcm_ops.o
|
||||||
|
@ -64,6 +64,9 @@ static void virtsnd_event_dispatch(struct virtio_snd *snd,
|
|||||||
case VIRTIO_SND_EVT_PCM_XRUN:
|
case VIRTIO_SND_EVT_PCM_XRUN:
|
||||||
virtsnd_pcm_event(snd, event);
|
virtsnd_pcm_event(snd, event);
|
||||||
break;
|
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)
|
if (rc)
|
||||||
return 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) {
|
if (snd->njacks) {
|
||||||
rc = virtsnd_jack_build_devs(snd);
|
rc = virtsnd_jack_build_devs(snd);
|
||||||
if (rc)
|
if (rc)
|
||||||
@ -251,6 +260,12 @@ static int virtsnd_build_devs(struct virtio_snd *snd)
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (snd->nkctls) {
|
||||||
|
rc = virtsnd_kctl_build_devs(snd);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
return snd_card_register(snd->card);
|
return snd_card_register(snd->card);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,10 +432,16 @@ static const struct virtio_device_id id_table[] = {
|
|||||||
{ 0 },
|
{ 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static unsigned int features[] = {
|
||||||
|
VIRTIO_SND_F_CTLS
|
||||||
|
};
|
||||||
|
|
||||||
static struct virtio_driver virtsnd_driver = {
|
static struct virtio_driver virtsnd_driver = {
|
||||||
.driver.name = KBUILD_MODNAME,
|
.driver.name = KBUILD_MODNAME,
|
||||||
.driver.owner = THIS_MODULE,
|
.driver.owner = THIS_MODULE,
|
||||||
.id_table = id_table,
|
.id_table = id_table,
|
||||||
|
.feature_table = features,
|
||||||
|
.feature_table_size = ARRAY_SIZE(features),
|
||||||
.validate = virtsnd_validate,
|
.validate = virtsnd_validate,
|
||||||
.probe = virtsnd_probe,
|
.probe = virtsnd_probe,
|
||||||
.remove = virtsnd_remove,
|
.remove = virtsnd_remove,
|
||||||
|
@ -31,6 +31,16 @@ struct virtio_snd_queue {
|
|||||||
struct virtqueue *vqueue;
|
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.
|
* struct virtio_snd - VirtIO sound card device.
|
||||||
* @vdev: Underlying virtio device.
|
* @vdev: Underlying virtio device.
|
||||||
@ -45,6 +55,9 @@ struct virtio_snd_queue {
|
|||||||
* @nsubstreams: Number of PCM substreams.
|
* @nsubstreams: Number of PCM substreams.
|
||||||
* @chmaps: VirtIO channel maps.
|
* @chmaps: VirtIO channel maps.
|
||||||
* @nchmaps: Number of 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_snd {
|
||||||
struct virtio_device *vdev;
|
struct virtio_device *vdev;
|
||||||
@ -59,6 +72,9 @@ struct virtio_snd {
|
|||||||
u32 nsubstreams;
|
u32 nsubstreams;
|
||||||
struct virtio_snd_chmap_info *chmaps;
|
struct virtio_snd_chmap_info *chmaps;
|
||||||
u32 nchmaps;
|
u32 nchmaps;
|
||||||
|
struct virtio_snd_ctl_info *kctl_infos;
|
||||||
|
struct virtio_kctl *kctls;
|
||||||
|
u32 nkctls;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Message completion timeout in milliseconds (module parameter). */
|
/* 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_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 */
|
#endif /* VIRTIO_SND_CARD_H */
|
||||||
|
466
sound/virtio/virtio_kctl.c
Normal file
466
sound/virtio/virtio_kctl.c
Normal file
@ -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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user