/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }