aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBhalchandra Gajare <gajare@codeaurora.org>2019-08-21 15:09:43 -0700
committerEric Laurent <elaurent@google.com>2021-01-19 12:07:08 +0100
commitda302cc198d8b7f5cc60b943dd224c603318af97 (patch)
treea6b6560e253a48e79729541129d7eecf67b6dd47
parenteb387d7d1cfd3f9a4f412b220bdb622788327a2e (diff)
downloadtinyalsa-da302cc198d8b7f5cc60b943dd224c603318af97.tar.gz
tinyalsa: add support for Mixer plugins
Update the mixer framework to support plugins. Add ability for physical card to have mixer controls registered by either kernel or plugins. Split mixer controls into two groups, one for kernel registered (hw group) and other for plugin registered (virtual group). Bug: 166482201 Test: audio smoke tests CRs-Fixed: 2563258 Change-Id: I5a29eb8d7675f804c5d2f10da978f28f8c2cf134 Merged-In: I5a29eb8d7675f804c5d2f10da978f28f8c2cf134 Signed-off-by: Bhalchandra Gajare <gajare@codeaurora.org> (cherry picked from commit 4268f1b58f5bf534749a0721f6c22b4c5b330d7a)
-rw-r--r--Android.bp2
-rw-r--r--include/tinyalsa/asoundlib.h26
-rw-r--r--include/tinyalsa/mixer_plugin.h193
-rw-r--r--mixer.c465
-rw-r--r--mixer_hw.c122
-rw-r--r--mixer_io.h52
-rw-r--r--mixer_plugin.c495
7 files changed, 1266 insertions, 89 deletions
diff --git a/Android.bp b/Android.bp
index 01f1573..1b6afbf 100644
--- a/Android.bp
+++ b/Android.bp
@@ -8,6 +8,8 @@ cc_library {
},
srcs: [
"mixer.c",
+ "mixer_hw.c",
+ "mixer_plugin.c",
"pcm.c",
"pcm_hw.c",
"pcm_plugin.c",
diff --git a/include/tinyalsa/asoundlib.h b/include/tinyalsa/asoundlib.h
index 2ca74b2..063a256 100644
--- a/include/tinyalsa/asoundlib.h
+++ b/include/tinyalsa/asoundlib.h
@@ -153,6 +153,31 @@ enum mixer_ctl_type {
MIXER_CTL_TYPE_MAX,
};
+#define CTL_ELEM_ID_NAME_MAXLEN 44
+
+typedef int ctl_elem_iface_t;
+
+struct ctl_elem_id {
+ unsigned int numid;
+ ctl_elem_iface_t iface;
+ unsigned int device;
+ unsigned int subdevice;
+ unsigned char name[CTL_ELEM_ID_NAME_MAXLEN];
+ unsigned int index;
+};
+
+
+struct ctl_event {
+ int type;
+ union {
+ struct {
+ unsigned int mask;
+ struct ctl_elem_id id;
+ } elem;
+ unsigned char data8[60];
+ } data;
+};
+
/* Open and close a stream */
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, struct pcm_config *config);
@@ -317,6 +342,7 @@ int mixer_ctl_get_range_max(struct mixer_ctl *ctl);
int mixer_subscribe_events(struct mixer *mixer, int subscribe);
int mixer_wait_event(struct mixer *mixer, int timeout);
int mixer_consume_event(struct mixer *mixer);
+int mixer_read_event(struct mixer *mixer, struct ctl_event *ev);
#if defined(__cplusplus)
} /* extern "C" */
diff --git a/include/tinyalsa/mixer_plugin.h b/include/tinyalsa/mixer_plugin.h
new file mode 100644
index 0000000..e8ef91a
--- /dev/null
+++ b/include/tinyalsa/mixer_plugin.h
@@ -0,0 +1,193 @@
+/*
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+
+#ifndef __MIXER_PLUGIN_H__
+#define __MIXER_PLUGIN_H__
+
+#define MIXER_PLUGIN_OPEN_FN(name) \
+ int name##_open(struct mixer_plugin **plugin, \
+ unsigned int card)
+
+#define MIXER_PLUGIN_OPEN_FN_PTR() \
+ int (*mixer_plugin_open_fn) (struct mixer_plugin **plugin, \
+ unsigned int card) \
+
+struct mixer_plugin;
+
+typedef void (*event_callback)(struct mixer_plugin *);
+
+struct mixer_plugin_ops {
+ void (*close) (struct mixer_plugin **plugin);
+ int (*subscribe_events) (struct mixer_plugin *plugin,
+ event_callback event_cb);
+ ssize_t (*read_event) (struct mixer_plugin *plugin,
+ struct ctl_event *ev, size_t size);
+};
+
+struct snd_control {
+ ctl_elem_iface_t iface;
+ unsigned int access;
+ const char *name;
+ snd_ctl_elem_type_t type;
+ void *value;
+ int (*get) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_elem_value *ev);
+ int (*put) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_elem_value *ev);
+ uint32_t private_value;
+ void *private_data;
+};
+
+struct mixer_plugin {
+ unsigned int card;
+ struct mixer_plugin_ops *ops;
+ void *priv;
+
+ int eventfd;
+ int subscribed;
+ int event_cnt;
+
+ struct snd_control *controls;
+ unsigned int num_controls;
+};
+
+struct snd_value_enum {
+ unsigned int items;
+ char **texts;
+};
+
+struct snd_value_bytes {
+ unsigned int size;
+};
+
+struct snd_value_tlv_bytes {
+ unsigned int size;
+ int (*get) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_tlv *tlv);
+ int (*put) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_tlv *tlv);
+};
+
+struct snd_value_int {
+ unsigned int count;
+ int min;
+ int max;
+ int step;
+};
+
+/* static initializers */
+
+#define SND_VALUE_ENUM(etexts, eitems) \
+ {.texts = etexts, .items = eitems}
+
+#define SND_VALUE_BYTES(csize) \
+ {.size = csize }
+
+#define SND_VALUE_INTEGER(icount, imin, imax, istep) \
+ {.count = icount, .min = imin, .max = imax, .step = istep }
+
+#define SND_VALUE_TLV_BYTES(csize, cget, cput) \
+ {.size = csize, .get = cget, .put = cput }
+
+#define SND_CONTROL_ENUM(cname, cget, cput, cenum, priv_val, priv_data) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, \
+ .name = cname, .value = &cenum, .get = cget, .put = cput, \
+ .private_value = priv_val, .private_data = priv_data, \
+ }
+
+#define SND_CONTROL_BYTES(cname, cget, cput, cbytes, priv_val, priv_data) \
+ { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .type = SNDRV_CTL_ELEM_TYPE_BYTES, \
+ .name = cname, .value = &cbytes, .get = cget, .put = cput, \
+ .private_value = priv_val, .private_data = priv_data, \
+ }
+
+#define SND_CONTROL_INTEGER(cname, cget, cput, cint, priv_val, priv_data) \
+ { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .type = SNDRV_CTL_ELEM_TYPE_INTEGER, \
+ .name = cname, .value = &cint, .get = cget, .put = cput, \
+ .private_value = priv_val, .private_data = priv_data, \
+ }
+
+#define SND_CONTROL_TLV_BYTES(cname, cbytes, priv_val, priv_data) \
+ { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE, \
+ .type = SNDRV_CTL_ELEM_TYPE_BYTES, \
+ .name = cname, .value = &cbytes, \
+ .private_value = priv_val, .private_data = priv_data, \
+ }
+
+/* pointer based initializers */
+#define INIT_SND_CONTROL_INTEGER(c, cname, cget, cput, cint, pval, pdata) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_INTEGER; \
+ c->name = cname; c->value = &cint; c->get = cget; c->put = cput; \
+ c->private_value = pval; c->private_data = pdata; \
+ }
+
+#define INIT_SND_CONTROL_BYTES(c, cname, cget, cput, cint, pval, pdata) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_BYTES; \
+ c->name = cname; c->value = &cint; c->get = cget; c->put = cput; \
+ c->private_value = pval; c->private_data = pdata; \
+ }
+
+#define INIT_SND_CONTROL_ENUM(c, cname, cget, cput, cenum, pval, pdata) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; \
+ c->name = cname; c->value = cenum; c->get = cget; c->put = cput; \
+ c->private_value = pval; c->private_data = pdata; \
+ }
+#define INIT_SND_CONTROL_TLV_BYTES(c, cname, cbytes, priv_val, priv_data) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_BYTES; \
+ c->name = cname; c->value = &cbytes; \
+ c->private_value = priv_val; c->private_data = priv_data; \
+ }
+#endif /* end of __MIXER_PLUGIN_H__ */
diff --git a/mixer.c b/mixer.c
index f3fdb62..87747c5 100644
--- a/mixer.c
+++ b/mixer.c
@@ -40,6 +40,7 @@
#include <sys/ioctl.h>
#include <linux/ioctl.h>
+
#define __force
#define __bitwise
#define __user
@@ -50,122 +51,258 @@
#endif
#include <tinyalsa/asoundlib.h>
+#include "mixer_io.h"
struct mixer_ctl {
- struct mixer *mixer;
+ struct mixer_ctl_group *grp;
struct snd_ctl_elem_info *info;
char **ename;
bool info_retrieved;
};
-struct mixer {
- int fd;
- struct snd_ctl_card_info card_info;
+struct mixer_ctl_group {
struct snd_ctl_elem_info *elem_info;
struct mixer_ctl *ctl;
unsigned int count;
+ int event_cnt;
+
+ struct mixer_ops *ops;
+ void *data;
};
-void mixer_close(struct mixer *mixer)
+struct mixer {
+ int fd;
+ struct snd_ctl_card_info card_info;
+
+ /* hardware/physical mixer control group */
+ struct mixer_ctl_group *hw_grp;
+
+ /*
+ * Virutal mixer control group.
+ * Currently supports one virtual mixer (.so)
+ * per card. Could be extended to multiple
+ */
+ struct mixer_ctl_group *virt_grp;
+
+ unsigned int total_ctl_count;
+};
+
+static void mixer_grp_close(struct mixer_ctl_group *grp)
{
- unsigned int n,m;
+ unsigned int n, m;
- if (!mixer)
+ if (!grp)
return;
- if (mixer->fd >= 0)
- close(mixer->fd);
-
- if (mixer->ctl) {
- for (n = 0; n < mixer->count; n++) {
- if (mixer->ctl[n].ename) {
- unsigned int max = mixer->ctl[n].info->value.enumerated.items;
+ if (grp->ctl) {
+ for (n = 0; n < grp->count; n++) {
+ if (grp->ctl[n].ename) {
+ unsigned int max = grp->ctl[n].info->value.enumerated.items;
for (m = 0; m < max; m++)
- free(mixer->ctl[n].ename[m]);
- free(mixer->ctl[n].ename);
+ free(grp->ctl[n].ename[m]);
+ free(grp->ctl[n].ename);
}
}
- free(mixer->ctl);
+ free(grp->ctl);
}
- if (mixer->elem_info)
- free(mixer->elem_info);
+ if (grp->elem_info)
+ free(grp->elem_info);
+
+ free(grp);
+}
+
+void mixer_close(struct mixer *mixer)
+{
+ if (!mixer)
+ return;
+
+ if (mixer->fd >= 0 && mixer->hw_grp)
+ mixer->hw_grp->ops->close(mixer->hw_grp->data);
+ mixer_grp_close(mixer->hw_grp);
+
+ if (mixer->virt_grp)
+ mixer->virt_grp->ops->close(mixer->virt_grp->data);
+ mixer_grp_close(mixer->virt_grp);
free(mixer);
/* TODO: verify frees */
}
-struct mixer *mixer_open(unsigned int card)
+static int mixer_grp_open(struct mixer_ctl_group **ctl_grp,
+ struct mixer_ops *ops,
+ void *data, int *num_ctls_in_grp)
{
+ struct mixer_ctl_group *grp;
struct snd_ctl_elem_list elist;
struct snd_ctl_elem_id *eid = NULL;
- struct mixer *mixer = NULL;
unsigned int n;
- int fd;
- char fn[256];
+ int ret;
- snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
- fd = open(fn, O_RDWR);
- if (fd < 0)
- return 0;
+ grp = calloc(1, sizeof(*grp));
+ if (!grp)
+ return -ENOMEM;
memset(&elist, 0, sizeof(elist));
- if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
- goto fail;
-
- mixer = calloc(1, sizeof(*mixer));
- if (!mixer)
- goto fail;
-
- mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl));
- mixer->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info));
- if (!mixer->ctl || !mixer->elem_info)
- goto fail;
+ ret = ops->ioctl(data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist);
+ if (ret < 0)
+ goto err_get_elem_list;
- if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0)
- goto fail;
+ grp->ctl = calloc(elist.count, sizeof(struct mixer_ctl));
+ grp->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info));
+ if (!grp->ctl || !grp->elem_info) {
+ ret = -ENOMEM;
+ goto err_ctl_alloc;
+ }
- eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id));
- if (!eid)
- goto fail;
+ eid = calloc(elist.count, sizeof(*eid));
+ if (!eid) {
+ ret = -ENOMEM;
+ goto err_ctl_alloc;
+ }
- mixer->count = elist.count;
- mixer->fd = fd;
- elist.space = mixer->count;
+ grp->count = elist.count;
+ elist.space = grp->count;
elist.pids = eid;
- if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
- goto fail;
+ ret = ops->ioctl(data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist);
+ if (ret < 0)
+ goto err_ctl_alloc;
- for (n = 0; n < mixer->count; n++) {
- struct mixer_ctl *ctl = mixer->ctl + n;
+ for (n = 0; n < grp->count; n++) {
+ struct mixer_ctl *ctl = grp->ctl + n;
- ctl->mixer = mixer;
- ctl->info = mixer->elem_info + n;
+ ctl->grp = grp;
+ ctl->info = grp->elem_info + n;
ctl->info->id.numid = eid[n].numid;
strncpy((char *)ctl->info->id.name, (char *)eid[n].name,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
ctl->info->id.name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0;
}
+ grp->data = data;
+ grp->ops = ops;
+ *ctl_grp = grp;
+ *num_ctls_in_grp = grp->count;
+
+ free(eid);
+ return 0;
+
+err_ctl_alloc:
+
free(eid);
+ free(grp->elem_info);
+ free(grp->ctl);
+
+err_get_elem_list:
+
+ free(grp);
+ return ret;
+
+}
+
+static int mixer_do_hw_open(struct mixer *mixer, unsigned int card)
+{
+ struct mixer_ops *ops;
+ void *data;
+ int fd, ret, num_grp_ctls = 0;
+
+ mixer->fd = -1;
+ fd = mixer_hw_open(card, &data, &ops);
+ if (fd < 0)
+ return fd;
+
+ ret = ops->ioctl(data, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info);
+ if (ret < 0)
+ goto err_card_info;
+
+ ret = mixer_grp_open(&mixer->hw_grp, ops, data, &num_grp_ctls);
+ if (ret < 0)
+ goto err_card_info;
+
+ mixer->total_ctl_count += num_grp_ctls;
+
+ mixer->fd = fd;
+ return 0;
+
+err_card_info:
+ ops->close(data);
+ return ret;
+
+}
+
+static int mixer_do_plugin_open(struct mixer *mixer, unsigned int card,
+ int is_hw_open_failed)
+{
+ struct mixer_ops *ops;
+ void *data;
+ int ret, num_grp_ctls = 0;
+
+ ret = mixer_plugin_open(card, &data, &ops);
+ if (ret < 0)
+ return ret;
+
+ /* Get card_info if hw_open failed */
+ if (is_hw_open_failed) {
+ ret = ops->ioctl(data, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info);
+ if (ret < 0)
+ goto err_card_info;
+ }
+
+ ret = mixer_grp_open(&mixer->virt_grp, ops, data, &num_grp_ctls);
+ if (ret < 0)
+ goto err_card_info;
+
+ mixer->total_ctl_count += num_grp_ctls;
+ return 0;
+
+err_card_info:
+ ops->close(data);
+ return ret;
+
+}
+
+struct mixer *mixer_open(unsigned int card)
+{
+ struct mixer *mixer = NULL;
+ int h_status, v_status;
+
+ mixer = calloc(1, sizeof(*mixer));
+ if (!mixer)
+ goto fail;
+
+ /* open hardware node first */
+ h_status = mixer_do_hw_open(mixer, card);
+
+ /*
+ * open the virtual node even if hw_open fails
+ * since hw_open is expected to fail for virtual cards
+ * for which kernel does not register mixer node
+ */
+ //TODO: open virtual node only if mixer is defined under snd-card-def
+ v_status = mixer_do_plugin_open(mixer, card, h_status);
+
+ /* Fail mixer_open if both hw and plugin nodes cannot be opened */
+ if (h_status < 0 && v_status < 0)
+ goto fail;
+
return mixer;
fail:
- /* TODO: verify frees in failure case */
- if (eid)
- free(eid);
if (mixer)
mixer_close(mixer);
- else if (fd >= 0)
- close(fd);
+
return 0;
}
static bool mixer_ctl_get_elem_info(struct mixer_ctl* ctl)
{
+ struct mixer_ctl_group *grp = ctl->grp;
+ unsigned int i;
+
if (!ctl->info_retrieved) {
- if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info) < 0)
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO,
+ ctl->info) < 0)
return false;
ctl->info_retrieved = true;
}
@@ -178,11 +315,11 @@ static bool mixer_ctl_get_elem_info(struct mixer_ctl* ctl)
if (!enames)
return false;
- for (unsigned int i = 0; i < ctl->info->value.enumerated.items; i++) {
+ for (i = 0; i < ctl->info->value.enumerated.items; i++) {
memset(&tmp, 0, sizeof(tmp));
tmp.id.numid = ctl->info->id.numid;
tmp.value.enumerated.item = i;
- if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
goto fail;
enames[i] = strdup(tmp.value.enumerated.name);
if (!enames[i])
@@ -206,17 +343,35 @@ unsigned int mixer_get_num_ctls(struct mixer *mixer)
if (!mixer)
return 0;
- return mixer->count;
+ return mixer->total_ctl_count;
+}
+
+static int mixer_grp_get_count(struct mixer_ctl_group *grp)
+{
+ if (!grp)
+ return 0;
+
+ return grp->count;
}
struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
{
struct mixer_ctl *ctl;
+ unsigned int hw_ctl_count, virt_ctl_count;
+
+ if (!mixer || (id >= mixer->total_ctl_count))
+ return NULL;
+
+ hw_ctl_count = mixer_grp_get_count(mixer->hw_grp);
+ virt_ctl_count = mixer_grp_get_count(mixer->virt_grp);
- if (!mixer || (id >= mixer->count))
+ if (id < hw_ctl_count)
+ ctl = mixer->hw_grp->ctl + id;
+ else if ((id - hw_ctl_count) < virt_ctl_count)
+ ctl = mixer->virt_grp->ctl + (id - hw_ctl_count);
+ else
return NULL;
- ctl = mixer->ctl + id;
if (!mixer_ctl_get_elem_info(ctl))
return NULL;
@@ -225,21 +380,41 @@ struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name)
{
+ struct mixer_ctl_group *grp;
unsigned int n;
+ int hw_ctl_count = mixer_grp_get_count(mixer->hw_grp);
if (!mixer)
return NULL;
- for (n = 0; n < mixer->count; n++)
- if (!strcmp(name, (char*) mixer->elem_info[n].id.name))
- return mixer_get_ctl(mixer, n);
+ if (mixer->hw_grp) {
+ grp = mixer->hw_grp;
+
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) grp->elem_info[n].id.name))
+ return mixer_get_ctl(mixer, n);
+ }
+
+ if (mixer->virt_grp) {
+ grp = mixer->virt_grp;
+
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) grp->elem_info[n].id.name))
+ return mixer_get_ctl(mixer, n + hw_ctl_count);
+ }
return NULL;
}
void mixer_ctl_update(struct mixer_ctl *ctl)
{
- ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info);
+ struct mixer_ctl_group *grp;
+
+ if (!ctl || !ctl->grp)
+ return;
+ grp = ctl->grp;
+
+ grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info);
}
const char *mixer_ctl_get_name(struct mixer_ctl *ctl)
@@ -332,6 +507,7 @@ int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent)
int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id)
{
+ struct mixer_ctl_group *grp = ctl->grp;
struct snd_ctl_elem_value ev;
int ret;
@@ -340,7 +516,7 @@ int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id)
memset(&ev, 0, sizeof(ev));
ev.id.numid = ctl->info->id.numid;
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
if (ret < 0)
return ret;
@@ -371,6 +547,7 @@ int mixer_ctl_is_access_tlv_rw(struct mixer_ctl *ctl)
int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count)
{
+ struct mixer_ctl_group *grp = ctl->grp;
struct snd_ctl_elem_value ev;
int ret = 0;
size_t size;
@@ -397,7 +574,7 @@ int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count)
switch (ctl->info->type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
case SNDRV_CTL_ELEM_TYPE_INTEGER:
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
if (ret < 0)
return ret;
size = sizeof(ev.value.integer.value[0]);
@@ -417,7 +594,7 @@ int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count)
return -ENOMEM;
tlv->numid = ctl->info->id.numid;
tlv->length = count;
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_READ, tlv);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_READ, tlv);
source = tlv->tlv;
memcpy(array, source, count);
@@ -426,7 +603,7 @@ int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count)
return ret;
} else {
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
if (ret < 0)
return ret;
size = sizeof(ev.value.bytes.data[0]);
@@ -450,6 +627,8 @@ int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count)
int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
{
+ struct mixer_ctl_group *grp = ctl->grp;
+
struct snd_ctl_elem_value ev;
int ret;
@@ -458,7 +637,7 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
memset(&ev, 0, sizeof(ev));
ev.id.numid = ctl->info->id.numid;
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
if (ret < 0)
return ret;
@@ -483,11 +662,12 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
return -EINVAL;
}
- return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+ return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
}
int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
{
+ struct mixer_ctl_group *grp = ctl->grp;
struct snd_ctl_elem_value ev;
size_t size;
void *dest;
@@ -531,7 +711,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
tlv->length = count;
memcpy(tlv->tlv, array, count);
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_WRITE, tlv);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_WRITE, tlv);
free(tlv);
return ret;
@@ -552,7 +732,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
memcpy(dest, array, size * count);
- return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+ return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
}
int mixer_ctl_get_range_min(struct mixer_ctl *ctl)
@@ -591,6 +771,7 @@ const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl,
int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
{
+ struct mixer_ctl_group *grp = ctl->grp;
unsigned int i, num_enums;
struct snd_ctl_elem_value ev;
int ret;
@@ -604,7 +785,7 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
memset(&ev, 0, sizeof(ev));
ev.value.enumerated.item[0] = i;
ev.id.numid = ctl->info->id.numid;
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
if (ret < 0)
return ret;
return 0;
@@ -623,8 +804,22 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
*/
int mixer_subscribe_events(struct mixer *mixer, int subscribe)
{
- if (ioctl(mixer->fd, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) {
- return -1;
+ struct mixer_ctl_group *grp;
+
+ if (mixer->hw_grp) {
+ grp = mixer->hw_grp;
+ if (!subscribe)
+ grp->event_cnt = 0;
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
+ return -1;
+ }
+
+ if (mixer->virt_grp) {
+ grp = mixer->virt_grp;
+ if (!subscribe)
+ grp->event_cnt = 0;
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
+ return -1;
}
return 0;
}
@@ -639,22 +834,58 @@ int mixer_subscribe_events(struct mixer *mixer, int subscribe)
*/
int mixer_wait_event(struct mixer *mixer, int timeout)
{
- struct pollfd pfd;
+ struct pollfd pfd[2];
+ struct mixer_ctl_group *grp;
+ int count = 0, num_fds = 0, i;
+
+ memset(pfd, 0, sizeof(struct pollfd) * 2);
+
+ if (mixer->fd >= 0)
+ num_fds++;
+
+ if (mixer->virt_grp)
+ num_fds++;
+
+
+ /* TODO wait events for virt fd */
+ if (mixer->fd >= 0) {
+ pfd[count].fd = mixer->fd;
+ pfd[count].events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
+ count++;
+ }
+
+ if (mixer->virt_grp) {
+ grp = mixer->virt_grp;
+ if (!grp->ops->get_poll_fd(grp->data, pfd, count)) {
+ pfd[count].events = POLLIN | POLLERR | POLLNVAL;
+ count++;
+ }
+ }
- pfd.fd = mixer->fd;
- pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
+ if (!count)
+ return 0;
for (;;) {
int err;
- err = poll(&pfd, 1, timeout);
+ err = poll(pfd, count, timeout);
if (err < 0)
return -errno;
if (!err)
return 0;
- if (pfd.revents & (POLLERR | POLLNVAL))
- return -EIO;
- if (pfd.revents & (POLLIN | POLLOUT))
- return 1;
+ for (i = 0; i < count; i++) {
+ if (pfd[i].revents & (POLLERR | POLLNVAL))
+ return -EIO;
+ if (pfd[i].revents & (POLLIN | POLLOUT)) {
+ if ((i == 0) && mixer->fd >= 0) {
+ grp = mixer->hw_grp;
+ grp->event_cnt++;
+ } else {
+ grp = mixer->virt_grp;
+ grp->event_cnt++;
+ }
+ return 1;
+ }
+ }
}
}
@@ -668,13 +899,69 @@ int mixer_wait_event(struct mixer *mixer, int timeout)
* @returns 0 on success. -errno on failure.
* @ingroup libtinyalsa-mixer
*/
-int mixer_consume_event(struct mixer *mixer) {
+int mixer_consume_event(struct mixer *mixer)
+{
struct snd_ctl_event ev;
- ssize_t count = read(mixer->fd, &ev, sizeof(ev));
+ struct mixer_ctl_group *grp;
+ ssize_t count = 0;
+
// Exporting the actual event would require exposing snd_ctl_event
// via the header file, and all associated structs.
// The events generally tell you exactly which value changed,
// but reading values you're interested isn't hard and simplifies
// the interface greatly.
- return (count >= 0) ? 0 : -errno;
+ if (mixer->hw_grp) {
+ grp = mixer->hw_grp;
+ if (grp->event_cnt) {
+ grp->event_cnt--;
+ count = grp->ops->read_event(grp->data, &ev, sizeof(ev));
+ return (count >= 0) ? 0 : -errno;
+ }
+ }
+
+ if (mixer->virt_grp) {
+ grp = mixer->virt_grp;
+ if (grp->event_cnt) {
+ grp->event_cnt--;
+ count += grp->ops->read_event(grp->data, &ev, sizeof(ev));
+ return (count >= 0) ? 0 : -errno;
+ }
+ }
+ return 0;
+}
+
+/** Read a mixer event.
+ * If mixer_subscribe_events has been called,
+ * mixer_wait_event will identify when a control value has changed.
+ * This function will read and clear a single event from the mixer
+ * so that further events can be alerted.
+ *
+ * @param mixer A mixer handle.
+ * @param ev snd_ctl_event pointer where event needs to be read
+ * @returns 0 on success. -errno on failure.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_read_event(struct mixer *mixer, struct ctl_event *ev)
+{
+ struct mixer_ctl_group *grp;
+ ssize_t count = 0;
+
+ if (mixer->hw_grp) {
+ grp = mixer->hw_grp;
+ if (grp->event_cnt) {
+ grp->event_cnt--;
+ count = grp->ops->read_event(grp->data, (struct snd_ctl_event *)ev, sizeof(*ev));
+ return (count >= 0) ? 0 : -errno;
+ }
+ }
+
+ if (mixer->virt_grp) {
+ grp = mixer->virt_grp;
+ if (grp->event_cnt) {
+ grp->event_cnt--;
+ count = grp->ops->read_event(grp->data, (struct snd_ctl_event *)ev, sizeof(*ev));
+ return (count >= 0) ? 0 : -errno;
+ }
+ }
+ return 0;
}
diff --git a/mixer_hw.c b/mixer_hw.c
new file mode 100644
index 0000000..9e01647
--- /dev/null
+++ b/mixer_hw.c
@@ -0,0 +1,122 @@
+/* mixer_hw.c
+**
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <poll.h>
+
+#include <sys/ioctl.h>
+
+#include <linux/ioctl.h>
+#include <sound/asound.h>
+
+#include "mixer_io.h"
+
+struct mixer_hw_data {
+ unsigned int card;
+ int fd;
+};
+
+static void mixer_hw_close(void *data)
+{
+ struct mixer_hw_data *hw_data = data;
+
+ if (!hw_data)
+ return;
+
+ if (hw_data->fd >= 0)
+ close(hw_data->fd);
+
+ hw_data->fd = -1;
+ free(hw_data);
+ hw_data = NULL;
+}
+
+static int mixer_hw_ioctl(void *data, unsigned int cmd, ...)
+{
+ struct mixer_hw_data *hw_data = data;
+ va_list ap;
+ void *arg;
+
+ va_start(ap, cmd);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ return ioctl(hw_data->fd, cmd, arg);
+}
+
+static ssize_t mixer_hw_read_event(void *data, struct snd_ctl_event *ev,
+ size_t size)
+{
+ struct mixer_hw_data *hw_data = data;
+
+ return read(hw_data->fd, ev, size);
+}
+
+static struct mixer_ops mixer_hw_ops = {
+ .close = mixer_hw_close,
+ .get_poll_fd = NULL,
+ .read_event = mixer_hw_read_event,
+ .ioctl = mixer_hw_ioctl,
+};
+
+int mixer_hw_open(unsigned int card, void **data,
+ struct mixer_ops **ops)
+{
+ struct mixer_hw_data *hw_data;
+ int fd;
+ char fn[256];
+
+ snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
+ fd = open(fn, O_RDWR);
+ if (fd < 0)
+ return fd;
+
+ hw_data = calloc(1, sizeof(*hw_data));
+ if (!hw_data) {
+ close(fd);
+ return -1;
+ }
+
+ hw_data->card = card;
+ hw_data->fd = fd;
+ *data = hw_data;
+ *ops = &mixer_hw_ops;
+
+ return fd;
+}
diff --git a/mixer_io.h b/mixer_io.h
new file mode 100644
index 0000000..77daae0
--- /dev/null
+++ b/mixer_io.h
@@ -0,0 +1,52 @@
+/* mixer_io.h
+**
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#ifndef __MIXER_H__
+#define __MIXER_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sound/asound.h>
+
+struct mixer_ops;
+
+int mixer_hw_open(unsigned int card, void **data,
+ struct mixer_ops **ops);
+int mixer_plugin_open(unsigned int card, void **data,
+ struct mixer_ops **ops);
+
+struct mixer_ops {
+ void (*close) (void *data);
+ int (*get_poll_fd) (void *data, struct pollfd *pfd, int count);
+ ssize_t (*read_event) (void *data, struct snd_ctl_event *ev, size_t size);
+ int (*ioctl) (void *data, unsigned int cmd, ...);
+};
+
+#endif /* end of __MIXER_H__ */
diff --git a/mixer_plugin.c b/mixer_plugin.c
new file mode 100644
index 0000000..a5e0660
--- /dev/null
+++ b/mixer_plugin.c
@@ -0,0 +1,495 @@
+/* mixer_plugin.c
+**
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <poll.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+
+#include <linux/ioctl.h>
+#include <sound/asound.h>
+
+#include <tinyalsa/asoundlib.h>
+#include <tinyalsa/mixer_plugin.h>
+#include "snd_utils.h"
+
+#include "mixer_io.h"
+
+struct mixer_plug_data {
+ int card;
+ void *mixer_node;
+
+ struct mixer_plugin *plugin;
+ void *dl_hdl;
+ MIXER_PLUGIN_OPEN_FN_PTR();
+};
+
+static int mixer_plug_get_elem_id(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_id *id, unsigned int offset)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+
+ if (offset >= plugin->num_controls) {
+ printf("%s: invalid offset %u\n", __func__, offset);
+ return -EINVAL;
+ }
+
+ ctl = plugin->controls + offset;
+ id->numid = offset;
+ id->iface = ctl->iface;
+
+ strncpy((char *)id->name, (char *)ctl->name,
+ sizeof(id->name));
+
+ return 0;
+}
+
+static int mixer_plug_info_enum(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_enum *val = ctl->value;
+
+ einfo->count = 1;
+ einfo->value.enumerated.items = val->items;
+
+ if (einfo->value.enumerated.item > val->items)
+ return -EINVAL;
+
+ strncpy(einfo->value.enumerated.name,
+ val->texts[einfo->value.enumerated.item],
+ sizeof(einfo->value.enumerated.name));
+
+ return 0;
+}
+
+static int mixer_plug_info_bytes(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_bytes *val;
+ struct snd_value_tlv_bytes *val_tlv;
+
+ if (ctl->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) {
+ val_tlv = ctl->value;
+ einfo->count = val_tlv->size;
+ } else {
+ val = ctl->value;
+ einfo->count = val->size;
+ }
+
+ return 0;
+}
+
+static int mixer_plug_info_integer(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_int *val = ctl->value;
+
+ einfo->count = val->count;
+ einfo->value.integer.min = val->min;
+ einfo->value.integer.max = val->max;
+ einfo->value.integer.step = val->step;
+
+ return 0;
+}
+
+void mixer_plug_notifier_cb(struct mixer_plugin *plugin)
+{
+ eventfd_write(plugin->eventfd, 1);
+ plugin->event_cnt++;
+}
+
+/* In consume_event/read, do not call eventfd_read until all events are read from list.
+ This will make poll getting unblocked until all events are read */
+static ssize_t mixer_plug_read_event(void *data, struct snd_ctl_event *ev, size_t size)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+ ssize_t result = 0;
+
+ result = plugin->ops->read_event(plugin, (struct ctl_event *)ev, size);
+
+ if (result > 0) {
+ plugin->event_cnt -= result / sizeof(struct snd_ctl_event);
+ if (plugin->event_cnt == 0)
+ eventfd_read(plugin->eventfd, &evfd);
+ }
+
+ return result;
+}
+
+static int mixer_plug_subscribe_events(struct mixer_plug_data *plug_data,
+ int *subscribe)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+
+ if (*subscribe < 0 || *subscribe > 1) {
+ *subscribe = plugin->subscribed;
+ return -EINVAL;
+ }
+
+ if (*subscribe && !plugin->subscribed) {
+ plugin->ops->subscribe_events(plugin, &mixer_plug_notifier_cb);
+ } else if (plugin->subscribed && !*subscribe) {
+ plugin->ops->subscribe_events(plugin, NULL);
+
+ if (plugin->event_cnt)
+ eventfd_read(plugin->eventfd, &evfd);
+
+ plugin->event_cnt = 0;
+ }
+
+ plugin->subscribed = *subscribe;
+ return 0;
+}
+
+static int mixer_plug_get_poll_fd(void *data, struct pollfd *pfd, int count)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+
+ if (plugin->eventfd != -1) {
+ pfd[count].fd = plugin->eventfd;
+ return 0;
+ }
+ return -ENODEV;
+}
+
+static int mixer_plug_tlv_write(struct mixer_plug_data *plug_data,
+ struct snd_ctl_tlv *tlv)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ struct snd_value_tlv_bytes *val_tlv;
+
+ ctl = plugin->controls + tlv->numid;
+ val_tlv = ctl->value;
+
+ return val_tlv->put(plugin, ctl, tlv);
+}
+
+static int mixer_plug_tlv_read(struct mixer_plug_data *plug_data,
+ struct snd_ctl_tlv *tlv)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ struct snd_value_tlv_bytes *val_tlv;
+
+ ctl = plugin->controls + tlv->numid;
+ val_tlv = ctl->value;
+
+ return val_tlv->get(plugin, ctl, tlv);
+}
+
+static int mixer_plug_elem_write(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_value *ev)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ int ret;
+
+ ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid);
+ if (ret < 0)
+ return ret;
+
+ ctl = plugin->controls + ev->id.numid;
+
+ return ctl->put(plugin, ctl, ev);
+}
+
+static int mixer_plug_elem_read(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_value *ev)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ int ret;
+
+ ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid);
+ if (ret < 0)
+ return ret;
+
+ ctl = plugin->controls + ev->id.numid;
+
+ return ctl->get(plugin, ctl, ev);
+
+}
+
+static int mixer_plug_get_elem_info(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ int ret;
+
+ ret = mixer_plug_get_elem_id(plug_data, &einfo->id,
+ einfo->id.numid);
+ if (ret < 0)
+ return ret;
+
+ ctl = plugin->controls + einfo->id.numid;
+ einfo->type = ctl->type;
+ einfo->access = ctl->access;
+
+ switch (einfo->type) {
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ ret = mixer_plug_info_enum(ctl, einfo);
+ if (ret < 0)
+ return ret;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_BYTES:
+ ret = mixer_plug_info_bytes(ctl, einfo);
+ if (ret < 0)
+ return ret;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ ret = mixer_plug_info_integer(ctl, einfo);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ printf("%s: unknown type %d\n", __func__, einfo->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mixer_plug_get_elem_list(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_list *elist)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ unsigned int avail;
+ struct snd_ctl_elem_id *id;
+ int ret;
+
+ elist->count = plugin->num_controls;
+ elist->used = 0;
+ avail = elist->space;
+
+ while (avail > 0) {
+ id = elist->pids + elist->used;
+ ret = mixer_plug_get_elem_id(plug_data, id, elist->used);
+ if (ret < 0)
+ return ret;
+
+ avail--;
+ elist->used++;
+ }
+
+ return 0;
+}
+
+static int mixer_plug_get_card_info(struct mixer_plug_data *plug_data,
+ struct snd_ctl_card_info *card_info)
+{
+ /*TODO: Fill card_info here from snd-card-def */
+ memset(card_info, 0, sizeof(*card_info));
+ card_info->card = plug_data->card;
+ memcpy(card_info->id, "card_id", sizeof(card_info->id));
+ memcpy(card_info->driver, "mymixer-so-name", sizeof(card_info->driver));
+ memcpy(card_info->name, "card-name", sizeof(card_info->name));
+ memcpy(card_info->longname, "card-name", sizeof(card_info->longname));
+ memcpy(card_info->mixername, "mixer-name", sizeof(card_info->mixername));
+
+ printf("%s: card = %d\n", __func__, plug_data->card);
+
+ return 0;
+}
+
+static void mixer_plug_close(void *data)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+
+ if (plugin->event_cnt)
+ eventfd_read(plugin->eventfd, &evfd);
+
+ plugin->ops->close(&plugin);
+ dlclose(plug_data->dl_hdl);
+ snd_utils_put_dev_node(plug_data->mixer_node);
+ free(plug_data);
+ plug_data = NULL;
+}
+
+static int mixer_plug_ioctl(void *data, unsigned int cmd, ...)
+{
+ struct mixer_plug_data *plug_data = data;
+ int ret;
+ va_list ap;
+ void *arg;
+
+ va_start(ap, cmd);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ switch (cmd) {
+ case SNDRV_CTL_IOCTL_CARD_INFO:
+ ret = mixer_plug_get_card_info(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_LIST:
+ ret = mixer_plug_get_elem_list(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_INFO:
+ ret = mixer_plug_get_elem_info(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_READ:
+ ret = mixer_plug_elem_read(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_WRITE:
+ ret = mixer_plug_elem_write(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_TLV_READ:
+ ret = mixer_plug_tlv_read(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_TLV_WRITE:
+ ret = mixer_plug_tlv_write(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
+ ret = mixer_plug_subscribe_events(plug_data, arg);
+ break;
+ default:
+ /* TODO: plugin should support ioctl */
+ ret = -EFAULT;
+ break;
+ }
+
+ return ret;
+}
+
+static struct mixer_ops mixer_plug_ops = {
+ .close = mixer_plug_close,
+ .get_poll_fd = mixer_plug_get_poll_fd,
+ .read_event = mixer_plug_read_event,
+ .ioctl = mixer_plug_ioctl,
+};
+
+int mixer_plugin_open(unsigned int card, void **data,
+ struct mixer_ops **ops)
+{
+ struct mixer_plug_data *plug_data;
+ struct mixer_plugin *plugin = NULL;
+ const char *err = NULL;
+ void *dl_hdl;
+ char *name, *so_name;
+ char *open_fn_name, token[80];
+ int ret;
+
+ plug_data = calloc(1, sizeof(*plug_data));
+ if (!plug_data)
+ return -ENOMEM;
+
+ /* mixer id is fixed to 1 in snd-card-def xml */
+ plug_data->mixer_node = snd_utils_get_dev_node(card, 1, NODE_MIXER);
+ if (!plug_data->mixer_node) {
+ /* Do not print error here.
+ * It is valid for card to not have virtual mixer node
+ */
+ goto err_get_mixer_node;
+ }
+
+ ret = snd_utils_get_str(plug_data->mixer_node, "so-name",
+ &so_name);
+ if(ret) {
+ fprintf(stderr, "%s: mixer so-name not found for card %u\n",
+ __func__, card);
+ goto err_get_mixer_node;
+
+ }
+
+ dl_hdl = dlopen(so_name, RTLD_NOW);
+ if (!dl_hdl) {
+ fprintf(stderr, "%s: unable to open %s\n",
+ __func__, so_name);
+ goto err_get_mixer_node;
+ }
+
+ sscanf(so_name, "lib%s", token);
+ name = strtok(token, ".");
+
+ open_fn_name = calloc(1, strlen(name) + strlen("_open") + 1);
+ if (!open_fn_name) {
+ ret = -ENOMEM;
+ goto err_get_mixer_node;
+ }
+
+ strncpy(open_fn_name, name, strlen(name) + 1);
+ strcat(open_fn_name, "_open");
+
+ printf("%s - %s\n", __func__, open_fn_name);
+
+ dlerror();
+ plug_data->mixer_plugin_open_fn = dlsym(dl_hdl, open_fn_name);
+ if (err) {
+ fprintf(stderr, "%s: dlsym open fn failed: %s\n",
+ __func__, err);
+ goto err_get_name;
+ }
+ ret = plug_data->mixer_plugin_open_fn(&plugin, card);
+ if (ret) {
+ fprintf(stderr, "%s: failed to open plugin, err: %d\n",
+ __func__, ret);
+ goto err_get_name;
+ }
+
+ plug_data->plugin = plugin;
+ plug_data->card = card;
+ plug_data->dl_hdl = dl_hdl;
+ plugin->eventfd = eventfd(0, 0);
+
+ *data = plug_data;
+ *ops = &mixer_plug_ops;
+
+ printf("%s: card = %d\n", __func__, plug_data->card);
+
+ return 0;
+
+err_get_name:
+ snd_utils_put_dev_node(plug_data->mixer_node);
+ dlclose(dl_hdl);
+
+err_get_mixer_node:
+
+ free(plug_data);
+ return -1;
+}