506 lines
14 KiB
C
506 lines
14 KiB
C
/* mixer_plugin.c
|
|
**
|
|
** Copyright (c) 2019-2020, 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 <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)
|
|
{
|
|
plugin->event_cnt++;
|
|
eventfd_write(plugin->eventfd, 1);
|
|
}
|
|
|
|
/* 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) {
|
|
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", strlen("card_id") + 1);
|
|
memcpy(card_info->driver, "mymixer-so-name", strlen("mymixer-so-name") + 1);
|
|
memcpy(card_info->name, "card-name", strlen("card-name") + 1);
|
|
memcpy(card_info->longname, "card-name", strlen("card-name") + 1);
|
|
memcpy(card_info->mixername, "mixer-name", strlen("mixer-name") + 1);
|
|
|
|
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;
|
|
void *dl_hdl;
|
|
char *name, *so_name;
|
|
char *open_fn_name, token[80], *token_saveptr;
|
|
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_free_plug_data;
|
|
}
|
|
|
|
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_put_dev_node;
|
|
|
|
}
|
|
|
|
dl_hdl = dlopen(so_name, RTLD_NOW);
|
|
if (!dl_hdl) {
|
|
fprintf(stderr, "%s: unable to open %s\n",
|
|
__func__, so_name);
|
|
goto err_put_dev_node;
|
|
}
|
|
|
|
sscanf(so_name, "lib%s", token);
|
|
token_saveptr = token;
|
|
name = strtok_r(token, ".", &token_saveptr);
|
|
if (!name) {
|
|
fprintf(stderr, "%s: invalid library name\n", __func__);
|
|
goto err_dl_hdl;
|
|
}
|
|
|
|
open_fn_name = calloc(1, strlen(name) + strlen("_open") + 1);
|
|
if (!open_fn_name) {
|
|
ret = -ENOMEM;
|
|
goto err_dl_hdl;
|
|
}
|
|
|
|
strncpy(open_fn_name, name, strlen(name) + 1);
|
|
strcat(open_fn_name, "_open");
|
|
|
|
printf("%s - %s\n", __func__, open_fn_name);
|
|
|
|
plug_data->mixer_plugin_open_fn = dlsym(dl_hdl, open_fn_name);
|
|
if (!plug_data->mixer_plugin_open_fn) {
|
|
fprintf(stderr, "%s: dlsym open fn failed: %s\n",
|
|
__func__, dlerror());
|
|
goto err_open_fn_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_open_fn_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);
|
|
|
|
free(open_fn_name);
|
|
return 0;
|
|
|
|
err_open_fn_name:
|
|
free(open_fn_name);
|
|
|
|
err_dl_hdl:
|
|
dlclose(dl_hdl);
|
|
|
|
err_put_dev_node:
|
|
snd_utils_put_dev_node(plug_data->mixer_node);
|
|
|
|
err_free_plug_data:
|
|
|
|
free(plug_data);
|
|
return -1;
|
|
}
|