From 986b8e338782a4c8aab83e626949ed5457eb5b69 Mon Sep 17 00:00:00 2001 From: Bhalchandra Gajare Date: Wed, 4 Sep 2019 15:32:35 -0700 Subject: tinyalsa: add support for PCM plugins Update the pcm framework to support plugins. Resolve the pcm device node to be either kernel device or virtual device and setup function pointers accordingly. Implement framework functionality for pcm_plugin.c for ease of plugin development. Plugin itself is compiled as shared object (.so) and dynamically linked from pcm_plugin.c. Signed-off-by: Bhalchandra Gajare Signed-off-by: Rohit kumar --- src/Makefile | 8 +- src/pcm.c | 121 ++++++--- src/pcm_hw.c | 118 +++++++++ src/pcm_io.h | 47 ++++ src/pcm_plugin.c | 718 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/snd_card_plugin.c | 149 +++++++++++ src/snd_card_plugin.h | 74 ++++++ 7 files changed, 1195 insertions(+), 40 deletions(-) create mode 100644 src/pcm_hw.c create mode 100644 src/pcm_io.h create mode 100644 src/pcm_plugin.c create mode 100644 src/snd_card_plugin.c create mode 100644 src/snd_card_plugin.h (limited to 'src') diff --git a/src/Makefile b/src/Makefile index aa00484..b278f47 100644 --- a/src/Makefile +++ b/src/Makefile @@ -15,7 +15,7 @@ INCLUDE_DIRS = -I ../include override CFLAGS := $(WARNINGS) $(INCLUDE_DIRS) -fPIC $(CFLAGS) VPATH = ../include/tinyalsa -OBJECTS = limits.o mixer.o pcm.o +OBJECTS = limits.o mixer.o pcm.o pcm_plugin.o pcm_hw.o snd_card_plugin.o LIBVERSION_MAJOR = $(TINYALSA_VERSION_MAJOR) LIBVERSION = $(TINYALSA_VERSION) @@ -25,10 +25,16 @@ all: libtinyalsa.a libtinyalsa.so pcm.o: pcm.c pcm.h +pcm_plugin.o: pcm_plugin.c pcm_io.h + +pcm_hw.o: pcm_hw.c pcm_io.h + limits.o: limits.c limits.h mixer.o: mixer.c mixer.h +snd_card_plugin.o: snd_card_plugin.c snd_card_plugin.h + libtinyalsa.a: $(OBJECTS) $(AR) $(ARFLAGS) $@ $^ diff --git a/src/pcm.c b/src/pcm.c index a32871e..7a8defc 100644 --- a/src/pcm.c +++ b/src/pcm.c @@ -59,6 +59,8 @@ #include #include +#include "pcm_io.h" +#include "snd_card_plugin.h" #ifndef PARAM_MAX #define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL @@ -68,6 +70,7 @@ #define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2) #endif /* SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP */ + static inline int param_is_mask(int p) { return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && @@ -234,6 +237,12 @@ struct pcm { long pcm_delay; /** The subdevice corresponding to the PCM */ unsigned int subdevice; + /** Pointer to the pcm ops, either hw or plugin */ + const struct pcm_ops *ops; + /** Private data for pcm_hw or pcm_plugin */ + void *data; + /** Pointer to the pcm node from snd card definition */ + struct snd_node *snd_node; }; static int oops(struct pcm *pcm, int e, const char *fmt, ...) @@ -383,7 +392,7 @@ int pcm_set_config(struct pcm *pcm, const struct pcm_config *config) param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, SNDRV_PCM_ACCESS_RW_INTERLEAVED); - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { int errno_copy = errno; oops(pcm, -errno, "cannot set hw params"); return -errno_copy; @@ -440,7 +449,7 @@ int pcm_set_config(struct pcm *pcm, const struct pcm_config *config) while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size) pcm->boundary *= 2; - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) { + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) { int errno_copy = errno; oops(pcm, -errno, "cannot set sw params"); return -errno_copy; @@ -519,7 +528,8 @@ static int pcm_sync_ptr(struct pcm *pcm, int flags) } } else { pcm->sync_ptr->flags = flags; - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0) { + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SYNC_PTR, + pcm->sync_ptr) < 0) { oops(pcm, errno, "failed to sync mmap ptr"); return -1; } @@ -599,19 +609,30 @@ struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, unsigned int flags) { struct snd_pcm_hw_params *params; - char fn[256]; + void *snd_node = NULL, *data; + const struct pcm_ops *ops; int fd; - snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, - flags & PCM_IN ? 'c' : 'p'); - - if (flags & PCM_NONBLOCK) - fd = open(fn, O_RDWR | O_NONBLOCK); - else - fd = open(fn, O_RDWR); + ops = &hw_ops; + fd = ops->open(card, device, flags, &data, snd_node); +#ifdef TINYALSA_USES_PLUGINS + if (fd < 0) { + int pcm_type; + snd_node = snd_utils_open_pcm(card, device); + pcm_type = snd_utils_get_node_type(snd_node); + if (!snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) { + fprintf(stderr, "no device (hw/plugin) for card(%u), device(%u)", + card, device); + goto err_open; + } + ops = &plug_ops; + fd = ops->open(card, device, flags, &data, snd_node); + } +#endif if (fd < 0) { - fprintf(stderr, "cannot open device '%s': %s\n", fn, strerror(errno)); + fprintf(stderr, "cannot open card(%d) device (%d): %s\n", + card, device, strerror(errno)); goto err_open; } @@ -620,19 +641,27 @@ struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, goto err_calloc; param_init(params); - if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) { + if (ops->ioctl(data, SNDRV_PCM_IOCTL_HW_REFINE, params)) { fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno); goto err_hw_refine; } - close(fd); +#ifdef TINYALSA_USES_PLUGINS + if (snd_node) + snd_utils_close_dev_node(snd_node); +#endif + ops->close(data); return (struct pcm_params *)params; err_hw_refine: free(params); err_calloc: - close(fd); +#ifdef TINYALSA_USES_PLUGINS + if (snd_node) + snd_utils_close_dev_node(snd_node); +#endif + ops->close(data); err_open: return NULL; } @@ -787,8 +816,8 @@ int pcm_close(struct pcm *pcm) munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); } - if (pcm->fd >= 0) - close(pcm->fd); + snd_utils_close_dev_node(pcm->snd_node); + pcm->ops->close(pcm->data); pcm->buffer_size = 0; pcm->fd = -1; free(pcm); @@ -851,29 +880,39 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, { struct pcm *pcm; struct snd_pcm_info info; - char fn[256]; int rc; pcm = calloc(1, sizeof(struct pcm)); if (!pcm) return &bad_pcm; - snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, - flags & PCM_IN ? 'c' : 'p'); - - pcm->flags = flags; - - if (flags & PCM_NONBLOCK) - pcm->fd = open(fn, O_RDWR | O_NONBLOCK); - else - pcm->fd = open(fn, O_RDWR); + /* Default to hw_ops, attemp plugin open only if hw (/dev/snd/pcm*) open fails */ + pcm->ops = &hw_ops; + pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, NULL); +#ifdef TINYALSA_USES_PLUGINS + if (pcm->fd < 0) { + int pcm_type; + pcm->snd_node = snd_utils_open_pcm(card, device); + pcm_type = snd_utils_get_node_type(pcm->snd_node); + if (!pcm->snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) { + oops(pcm, -ENODEV, "no device (hw/plugin) for card(%u), device(%u)", + card, device); + return pcm; + } + pcm->ops = &plug_ops; + pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node); + } +#endif if (pcm->fd < 0) { - oops(pcm, errno, "cannot open device '%s'", fn); + oops(pcm, errno, "cannot open device (%u) for card (%u)", + device, card); return pcm; } - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { + pcm->flags = flags; + + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)) { oops(pcm, errno, "cannot get info"); goto fail_close; } @@ -891,7 +930,7 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, #ifdef SNDRV_PCM_IOCTL_TTSTAMP if (pcm->flags & PCM_MONOTONIC) { int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC; - rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg); + rc = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_TTSTAMP, &arg); if (rc < 0) { oops(pcm, rc, "cannot set timestamp type"); goto fail; @@ -911,7 +950,11 @@ fail: if (flags & PCM_MMAP) munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); fail_close: - close(pcm->fd); +#ifdef TINYALSA_USES_PLUGINS + if (pcm->snd_node) + snd_utils_close_dev_node(pcm->snd_node); +#endif + pcm->ops->close(pcm->data); pcm->fd = -1; return pcm; } @@ -970,7 +1013,7 @@ int pcm_unlink(struct pcm *pcm) */ int pcm_prepare(struct pcm *pcm) { - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0) + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_PREPARE) < 0) return oops(pcm, errno, "cannot prepare channel"); /* get appl_ptr and avail_min from kernel */ @@ -991,7 +1034,7 @@ int pcm_start(struct pcm *pcm) return -1; if (pcm->mmap_status->state != PCM_STATE_RUNNING) { - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0) + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START) < 0) return oops(pcm, errno, "cannot start channel"); } @@ -1005,7 +1048,7 @@ int pcm_start(struct pcm *pcm) */ int pcm_stop(struct pcm *pcm) { - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0) + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_DROP) < 0) return oops(pcm, errno, "cannot stop channel"); return 0; @@ -1288,7 +1331,7 @@ int pcm_mmap_transfer(struct pcm *pcm, void *buffer, unsigned int frames) */ if (!is_playback && state == PCM_STATE_PREPARED && frames >= pcm->config.start_threshold) { - err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_START); + err = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START); if (err == -1) return -1; /* state = PCM_STATE_RUNNING */ @@ -1326,7 +1369,7 @@ int pcm_mmap_transfer(struct pcm *pcm, void *buffer, unsigned int frames) /* start playback if written >= start_threshold */ if (is_playback && state == PCM_STATE_PREPARED && pcm->buffer_size - avail >= pcm->config.start_threshold) { - err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_START); + err = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START); if (err == -1) break; } @@ -1365,9 +1408,9 @@ static int pcm_rw_transfer(struct pcm *pcm, void *data, unsigned int frames) transfer.frames = frames; transfer.result = 0; - res = ioctl(pcm->fd, is_playback - ? SNDRV_PCM_IOCTL_WRITEI_FRAMES - : SNDRV_PCM_IOCTL_READI_FRAMES, &transfer); + res = pcm->ops->ioctl(pcm->data, is_playback + ? SNDRV_PCM_IOCTL_WRITEI_FRAMES + : SNDRV_PCM_IOCTL_READI_FRAMES, &transfer); return res == 0 ? (int) transfer.result : -1; } diff --git a/src/pcm_hw.c b/src/pcm_hw.c new file mode 100644 index 0000000..1e67e9b --- /dev/null +++ b/src/pcm_hw.c @@ -0,0 +1,118 @@ +/* pcm_hw.c +** Copyright (c) 2019, The Linux Foundation. +** +** 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 "pcm_io.h" + +struct pcm_hw_data { + /** Card number of the pcm device */ + unsigned int card; + /** Device number for the pcm device */ + unsigned int device; + /** File descriptor to the pcm device file node */ + unsigned int fd; + /** Pointer to the pcm node from snd card definiton */ + struct snd_node *node; +}; + +static void pcm_hw_close(void *data) +{ + struct pcm_hw_data *hw_data = data; + + if (hw_data->fd > 0) + close(hw_data->fd); + + free(hw_data); +} + +static int pcm_hw_ioctl(void *data, unsigned int cmd, ...) +{ + struct pcm_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 int pcm_hw_open(unsigned int card, unsigned int device, + unsigned int flags, void **data, struct snd_node *node) +{ + struct pcm_hw_data *hw_data; + char fn[256]; + int fd; + + hw_data = calloc(1, sizeof(*hw_data)); + if (!hw_data) { + return -ENOMEM; + } + + snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, + flags & PCM_IN ? 'c' : 'p'); + if (flags & PCM_NONBLOCK) + fd = open(fn, O_RDWR|O_NONBLOCK); + else + fd = open(fn, O_RDWR); + + if (fd < 0) { + return fd; + } + + hw_data->card = card; + hw_data->device = device; + hw_data->fd = fd; + hw_data->node = node; + + *data = hw_data; + + return fd; +} + +const struct pcm_ops hw_ops = { + .open = pcm_hw_open, + .close = pcm_hw_close, + .ioctl = pcm_hw_ioctl, +}; + diff --git a/src/pcm_io.h b/src/pcm_io.h new file mode 100644 index 0000000..1b68ac8 --- /dev/null +++ b/src/pcm_io.h @@ -0,0 +1,47 @@ +/* pcm_io.h +** Copyright (c) 2019, The Linux Foundation. +** +** 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 TINYALSA_SRC_PCM_IO_H +#define TINYALSA_SRC_PCM_IO_H + +#include + +struct snd_node; + +struct pcm_ops { + int (*open) (unsigned int card, unsigned int device, + unsigned int flags, void **data, struct snd_node *node); + void (*close) (void *data); + int (*ioctl) (void *data, unsigned int cmd, ...); +}; + +extern const struct pcm_ops hw_ops; +extern const struct pcm_ops plug_ops; + +#endif /* TINYALSA_SRC_PCM_IO_H */ diff --git a/src/pcm_plugin.c b/src/pcm_plugin.c new file mode 100644 index 0000000..695ad2c --- /dev/null +++ b/src/pcm_plugin.c @@ -0,0 +1,718 @@ +/* pcm_plugin.c +** Copyright (c) 2019, The Linux Foundation. +** +** 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 "pcm_io.h" +#include "snd_card_plugin.h" + +/* 2 words of uint32_t = 64 bits of mask */ +#define PCM_MASK_SIZE (2) +#define ARRAY_SIZE(a) \ + (sizeof(a) / sizeof(a[0])) + +#define PCM_PARAM_GET_MASK(p, n) \ + &p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]; + +enum { + PCM_PLUG_HW_PARAM_SELECT_MIN, + PCM_PLUG_HW_PARAM_SELECT_MAX, + PCM_PLUG_HW_PARAM_SELECT_VAL, +}; + +enum { + PCM_PLUG_STATE_OPEN, + PCM_PLUG_STATE_SETUP, + PCM_PLUG_STATE_PREPARED, + PCM_PLUG_STATE_RUNNING, +}; + +struct pcm_plug_data { + unsigned int card; + unsigned int device; + unsigned int fd; + unsigned int flags; + + void *dl_hdl; + /** pointer to plugin operation */ + const struct pcm_plugin_ops *ops; + struct pcm_plugin *plugin; + void *dev_node; +}; + +static unsigned int param_list[] = { + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + SNDRV_PCM_HW_PARAM_PERIODS, +}; + +static void pcm_plug_close(void *data) +{ + struct pcm_plug_data *plug_data = data; + struct pcm_plugin *plugin = plug_data->plugin; + + plug_data->ops->close(plugin); + dlclose(plug_data->dl_hdl); + + free(plug_data); +} + +static int pcm_plug_info(struct pcm_plug_data *plug_data, + struct snd_pcm_info *info) +{ + int stream = SNDRV_PCM_STREAM_PLAYBACK; + int ret = 0, val = -1; + char *name; + + memset(info, 0, sizeof(*info)); + + if (plug_data->flags & PCM_IN) { + stream = SNDRV_PCM_STREAM_CAPTURE; + ret = snd_utils_get_int(plug_data->dev_node, "capture", &val); + if (ret || !val) { + fprintf(stderr, "%s: not a capture device\n", __func__); + return -EINVAL; + } + } else { + stream = SNDRV_PCM_STREAM_PLAYBACK; + ret = snd_utils_get_int(plug_data->dev_node, "playback", &val); + if (ret || !val) { + fprintf(stderr, "%s: not a playback device\n", __func__); + return -EINVAL; + } + } + + info->stream = stream; + info->card = plug_data->card; + info->device = plug_data->device; + + ret = snd_utils_get_str(plug_data->dev_node, "name", &name); + if (ret) { + fprintf(stderr, "%s: failed to get pcm device name\n", __func__); + return ret; + } + + strncpy((char *)info->id, name, sizeof(info->id)); + strncpy((char *)info->name, name, sizeof(info->name)); + strncpy((char *)info->subname, name, sizeof(info->subname)); + + info->subdevices_count = 1; + + return ret; +} + +static void pcm_plug_set_mask(struct snd_pcm_hw_params *p, int n, uint64_t v) +{ + struct snd_mask *mask; + + mask = PCM_PARAM_GET_MASK(p, n); + + mask->bits[0] |= (v & 0xFFFFFFFF); + mask->bits[1] |= ((v >> 32) & 0xFFFFFFFF); + /* + * currently only supporting 64 bits, may need to update to support + * more than 64 bits + */ +} + +static void pcm_plug_set_interval(struct snd_pcm_hw_params *params, + int p, struct pcm_plugin_min_max *v, int is_integer) +{ + struct snd_interval *i; + + i = ¶ms->intervals[p - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]; + + i->min = v->min; + i->max = v->max; + + if (is_integer) + i->integer = 1; +} + +static int pcm_plug_frames_to_bytes(unsigned int frames, + unsigned int frame_bits) +{ + return (frames * (frame_bits / 8)); +} + +static int pcm_plug_bytes_to_frames(unsigned int size, + unsigned int frame_bits) +{ + return (size * 8) / frame_bits; +} + +static int pcm_plug_get_params(struct pcm_plugin *plugin, + struct snd_pcm_hw_params *params) +{ + struct pcm_plugin_min_max bw, ch, pb, periods; + struct pcm_plugin_min_max val; + struct pcm_plugin_min_max frame_bits, buffer_bytes; + + /* + * populate the struct snd_pcm_hw_params structure + * using the hw_param constraints provided by plugin + * via the plugin->constraints + */ + + /* Set the mask params */ + pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS, + plugin->constraints->access); + pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT, + plugin->constraints->format); + pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT, + SNDRV_PCM_SUBFORMAT_STD); + + /* Set the standard interval params */ + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + &plugin->constraints->bit_width, 1); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS, + &plugin->constraints->channels, 1); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_RATE, + &plugin->constraints->rate, 1); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + &plugin->constraints->period_bytes, 0); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIODS, + &plugin->constraints->periods, 1); + + /* set the calculated interval params */ + + bw.min = plugin->constraints->bit_width.min; + bw.max = plugin->constraints->bit_width.max; + + ch.min = plugin->constraints->channels.min; + ch.max = plugin->constraints->channels.max; + + pb.min = plugin->constraints->period_bytes.min; + pb.max = plugin->constraints->period_bytes.max; + + periods.min = plugin->constraints->periods.min; + periods.max = plugin->constraints->periods.max; + + /* Calculate and set frame bits */ + frame_bits.min = bw.min * ch.min; + frame_bits.max = bw.max * ch.max; + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS, + &frame_bits, 1); + + + /* Calculate and set period_size in frames */ + val.min = pcm_plug_bytes_to_frames(pb.min, frame_bits.min); + val.max = pcm_plug_bytes_to_frames(pb.max, frame_bits.min); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + &val, 1); + + /* Calculate and set buffer_bytes */ + buffer_bytes.min = pb.min * periods.min; + buffer_bytes.max = pb.max * periods.max; + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &buffer_bytes, 1); + + /* Calculate and set buffer_size in frames */ + val.min = pcm_plug_bytes_to_frames(buffer_bytes.min, frame_bits.min); + val.max = pcm_plug_bytes_to_frames(buffer_bytes.max, frame_bits.min); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + &val, 1); + return 0; +} + +static int pcm_plug_masks_refine(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_params *c) +{ + struct snd_mask *req_mask; + struct snd_mask *con_mask; + unsigned int idx, i, masks; + + masks = SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK; + + for (idx = 0; idx <= masks; idx++) { + + if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK)))) + continue; + + req_mask = PCM_PARAM_GET_MASK(p, idx); + con_mask = PCM_PARAM_GET_MASK(c, idx); + + /* + * set the changed mask if requested mask value is not the same as + * constrained mask value + */ + if (memcmp(req_mask, con_mask, PCM_MASK_SIZE * sizeof(uint32_t))) + p->cmask |= 1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK); + + /* Actually change the requested mask to constrained mask */ + for (i = 0; i < PCM_MASK_SIZE; i++) + req_mask->bits[i] &= con_mask->bits[i]; + } + + return 0; +} + +static int pcm_plug_interval_refine(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_params *c) +{ + struct snd_interval *ri; + struct snd_interval *ci; + unsigned int idx; + unsigned int intervals; + int changed = 0; + + intervals = SNDRV_PCM_HW_PARAM_LAST_INTERVAL - + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; + + for (idx = 0; idx <= intervals; idx++) { + ri = &p->intervals[idx]; + ci = &c->intervals[idx]; + + if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL)) )) + continue; + + if (ri->min < ci->min) { + ri->min = ci->min; + ri->openmin = ci->openmin; + changed = 1; + } else if (ri->min == ci->min && !ri->openmin && ci->openmin) { + ri->openmin = 1; + changed = 1; + } + + if (ri->max > ci->max) { + ri->max = ci->max; + ri->openmax = ci->openmax; + changed = 1; + } else if (ri->max == ci->max && !ri->openmax && ci->openmax) { + ri->openmax = 1; + changed = 1; + }; + + if (!ri->integer && ci->integer) { + ri->integer = 1; + changed = 1; + } + + if (ri->integer) { + if (ri->openmin) { + ri->min++; + ri->openmin = 0; + } + if (ri->openmax) { + ri->max--; + ri->openmax = 0; + } + } else if (!ri->openmin && !ri->openmax && ri->min == ri->max) { + ri->integer = 1; + } + + /* Set the changed mask */ + if (changed) + p->cmask |= (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL)); + } + + return 0; +} + + +static int pcm_plug_hw_params_refine(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_params *c) +{ + int rc; + + rc = pcm_plug_masks_refine(p, c); + if (rc) { + fprintf(stderr, "%s: masks refine failed %d\n", __func__, rc); + return rc; + } + + rc = pcm_plug_interval_refine(p, c); + if (rc) { + fprintf(stderr, "%s: interval refine failed %d\n", __func__, rc); + return rc; + } + + /* clear the requested params */ + p->rmask = 0; + + return rc; +} + +static int __pcm_plug_hrefine(struct pcm_plug_data *plug_data, + struct snd_pcm_hw_params *params) +{ + struct pcm_plugin *plugin = plug_data->plugin; + struct snd_pcm_hw_params plug_params; + int rc; + + memset(&plug_params, 0, sizeof(plug_params)); + rc = pcm_plug_get_params(plugin, &plug_params); + if (rc) { + fprintf(stderr, "%s: pcm_plug_get_params failed %d\n", + __func__, rc); + return -EINVAL; + } + + return pcm_plug_hw_params_refine(params, &plug_params); + +} + +static int pcm_plug_hrefine(struct pcm_plug_data *plug_data, + struct snd_pcm_hw_params *params) +{ + return __pcm_plug_hrefine(plug_data, params); +} + +static int pcm_plug_interval_select(struct snd_pcm_hw_params *p, + unsigned int param, unsigned int select, unsigned int val) +{ + struct snd_interval *i; + + if (param < SNDRV_PCM_HW_PARAM_FIRST_INTERVAL || + param > SNDRV_PCM_HW_PARAM_LAST_INTERVAL) + return -EINVAL; + + i = &p->intervals[param - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]; + + if (!i->min) + return -EINVAL; + + switch (select) { + + case PCM_PLUG_HW_PARAM_SELECT_MIN: + i->max = i->min; + break; + + case PCM_PLUG_HW_PARAM_SELECT_MAX: + i->min = i->max; + break; + + case PCM_PLUG_HW_PARAM_SELECT_VAL: + i->min = i->max = val; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static void pcm_plug_hw_params_set(struct snd_pcm_hw_params *p) +{ + unsigned int i, select; + unsigned int bw, ch, period_sz, periods; + unsigned int val1, val2, offset; + + offset = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; + + /* Select the min values first */ + select = PCM_PLUG_HW_PARAM_SELECT_MIN; + for (i = 0; i < ARRAY_SIZE(param_list); i++) + pcm_plug_interval_select(p, param_list[i], select, 0); + + /* Select calculated values */ + select = PCM_PLUG_HW_PARAM_SELECT_VAL; + bw = (p->intervals[SNDRV_PCM_HW_PARAM_SAMPLE_BITS - offset]).min; + ch = (p->intervals[SNDRV_PCM_HW_PARAM_CHANNELS - offset]).min; + period_sz = (p->intervals[SNDRV_PCM_HW_PARAM_PERIOD_SIZE - offset]).min; + periods = (p->intervals[SNDRV_PCM_HW_PARAM_PERIODS - offset]).min; + + val1 = bw * ch; // frame_bits; + pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_FRAME_BITS, select, val1); + + val2 = pcm_plug_frames_to_bytes(period_sz, val1); // period_bytes; + pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, select, + val2); + + val2 = period_sz * periods; //buffer_size; + pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, select, val2); + + val2 = pcm_plug_frames_to_bytes(period_sz * periods, val1); //buffer_bytes; + pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, select, val2); +} + +static int pcm_plug_hparams(struct pcm_plug_data *plug_data, + struct snd_pcm_hw_params *params) +{ + struct pcm_plugin *plugin = plug_data->plugin; + int rc; + + if (plugin->state != PCM_PLUG_STATE_OPEN) + return -EBADFD; + + params->rmask = ~0U; + + rc = __pcm_plug_hrefine(plug_data, params); + if (rc) { + fprintf(stderr, "%s: __pcm_plug_hrefine failed %d\n", + __func__, rc); + return rc; + } + + pcm_plug_hw_params_set(params); + + rc = plug_data->ops->hw_params(plugin, params); + if (!rc) + plugin->state = PCM_PLUG_STATE_SETUP; + + return rc; +} + +static int pcm_plug_sparams(struct pcm_plug_data *plug_data, + struct snd_pcm_sw_params *params) +{ + struct pcm_plugin *plugin = plug_data->plugin; + + if (plugin->state != PCM_PLUG_STATE_SETUP) + return -EBADFD; + + return plug_data->ops->sw_params(plugin, params); +} + +static int pcm_plug_sync_ptr(struct pcm_plug_data *plug_data, + struct snd_pcm_sync_ptr *sync_ptr) +{ + struct pcm_plugin *plugin = plug_data->plugin; + + return plug_data->ops->sync_ptr(plugin, sync_ptr); +} + +static int pcm_plug_writei_frames(struct pcm_plug_data *plug_data, + struct snd_xferi *x) +{ + struct pcm_plugin *plugin = plug_data->plugin; + + if (plugin->state != PCM_PLUG_STATE_PREPARED && + plugin->state != PCM_PLUG_STATE_RUNNING) + return -EBADFD; + + return plug_data->ops->writei_frames(plugin, x); +} + +static int pcm_plug_readi_frames(struct pcm_plug_data *plug_data, + struct snd_xferi *x) +{ + struct pcm_plugin *plugin = plug_data->plugin; + + if (plugin->state != PCM_PLUG_STATE_RUNNING) + return -EBADFD; + + return plug_data->ops->readi_frames(plugin, x); +} + +static int pcm_plug_ttstamp(struct pcm_plug_data *plug_data, + int *tstamp) +{ + struct pcm_plugin *plugin = plug_data->plugin; + + if (plugin->state != PCM_PLUG_STATE_RUNNING) + return -EBADFD; + + return plug_data->ops->ttstamp(plugin, tstamp); +} + +static int pcm_plug_prepare(struct pcm_plug_data *plug_data) +{ + struct pcm_plugin *plugin = plug_data->plugin; + int rc; + + if (plugin->state != PCM_PLUG_STATE_SETUP) + return -EBADFD; + + rc = plug_data->ops->prepare(plugin); + if (!rc) + plugin->state = PCM_PLUG_STATE_PREPARED; + + return rc; +} + +static int pcm_plug_start(struct pcm_plug_data *plug_data) +{ + struct pcm_plugin *plugin = plug_data->plugin; + int rc; + + if (plugin->state != PCM_PLUG_STATE_PREPARED) + return -EBADFD; + + rc = plug_data->ops->start(plugin); + if (!rc) + plugin->state = PCM_PLUG_STATE_RUNNING; + + return rc; +} + +static int pcm_plug_drop(struct pcm_plug_data *plug_data) +{ + struct pcm_plugin *plugin = plug_data->plugin; + int rc; + + rc = plug_data->ops->drop(plugin); + if (!rc) + plugin->state = PCM_PLUG_STATE_SETUP; + + return rc; +} + +static int pcm_plug_ioctl(void *data, unsigned int cmd, ...) +{ + struct pcm_plug_data *plug_data = data; + struct pcm_plugin *plugin = plug_data->plugin; + int ret; + va_list ap; + void *arg; + + va_start(ap, cmd); + arg = va_arg(ap, void *); + va_end(ap); + + switch (cmd) { + case SNDRV_PCM_IOCTL_INFO: + ret = pcm_plug_info(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_TTSTAMP: + ret = pcm_plug_ttstamp(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_HW_REFINE: + ret = pcm_plug_hrefine(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_HW_PARAMS: + ret = pcm_plug_hparams(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_SW_PARAMS: + ret = pcm_plug_sparams(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_SYNC_PTR: + ret = pcm_plug_sync_ptr(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_PREPARE: + ret = pcm_plug_prepare(plug_data); + break; + case SNDRV_PCM_IOCTL_START: + ret = pcm_plug_start(plug_data); + break; + case SNDRV_PCM_IOCTL_DROP: + ret = pcm_plug_drop(plug_data); + break; + case SNDRV_PCM_IOCTL_WRITEI_FRAMES: + ret = pcm_plug_writei_frames(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_READI_FRAMES: + ret = pcm_plug_readi_frames(plug_data, arg); + break; + default: + ret = plug_data->ops->ioctl(plugin, cmd, arg); + break; + } + + return ret; +} + +static int pcm_plug_open(unsigned int card, unsigned int device, + unsigned int flags, void **data, struct snd_node *pcm_node) +{ + struct pcm_plug_data *plug_data; + void *dl_hdl; + int rc = 0; + char *so_name; + + plug_data = calloc(1, sizeof(*plug_data)); + if (!plug_data) { + return -ENOMEM; + } + + rc = snd_utils_get_str(pcm_node, "so-name", &so_name); + if (rc) { + fprintf(stderr, "%s: failed to get plugin lib name\n", __func__); + goto err_get_lib; + } + + dl_hdl = dlopen(so_name, RTLD_NOW); + if (!dl_hdl) { + fprintf(stderr, "%s: unable to open %s\n", __func__, so_name); + goto err_dl_open; + } else { + fprintf(stderr, "%s: dlopen successful for %s\n", __func__, so_name); + } + + dlerror(); + + plug_data->ops = dlsym(dl_hdl, "pcm_plugin_ops"); + if (!plug_data->ops) { + fprintf(stderr, "%s: dlsym to open fn failed, err = '%s'\n", + __func__, dlerror()); + goto err_dlsym; + } + + rc = plug_data->ops->open(&plug_data->plugin, card, device, flags); + if (rc) { + fprintf(stderr, "%s: failed to open plugin\n", __func__); + goto err_open; + } + + plug_data->dl_hdl = dl_hdl; + plug_data->card = card; + plug_data->device = device; + plug_data->dev_node = pcm_node; + plug_data->flags = flags; + + *data = plug_data; + + plug_data->plugin->state = PCM_PLUG_STATE_OPEN; + + return 0; + +err_open: +err_dlsym: + dlclose(dl_hdl); +err_get_lib: +err_dl_open: + free(plug_data); + + return rc; +} + +const struct pcm_ops plug_ops = { + .open = pcm_plug_open, + .close = pcm_plug_close, + .ioctl = pcm_plug_ioctl, +}; diff --git a/src/snd_card_plugin.c b/src/snd_card_plugin.c new file mode 100644 index 0000000..17912a2 --- /dev/null +++ b/src/snd_card_plugin.c @@ -0,0 +1,149 @@ +/* snd_card_plugin.c +** Copyright (c) 2019, The Linux Foundation. +** +** 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 "snd_card_plugin.h" + +#include +#include +#include + +#define SND_DLSYM(h, p, s, err) \ +do { \ + err = 0; \ + p = dlsym(h, s); \ + if (!p) \ + err = -ENODEV; \ +} while(0) + +int snd_utils_get_int(struct snd_node *node, const char *prop, int *val) +{ + if (!node || !node->card_node || !node->dev_node) + return SND_NODE_TYPE_HW; + + return node->ops->get_int(node->dev_node, prop, val); +} + +int snd_utils_get_str(struct snd_node *node, const char *prop, char **val) +{ + if (!node || !node->card_node || !node->dev_node) + return SND_NODE_TYPE_HW; + + return node->ops->get_str(node->dev_node, prop, val); +} + +void snd_utils_close_dev_node(struct snd_node *node) +{ + if (!node) + return; + + if (node->card_node) + node->ops->close_card(node->card_node); + + if (node->dl_hdl) + dlclose(node->dl_hdl); + + free(node); +} + +enum snd_node_type snd_utils_get_node_type(struct snd_node *node) +{ + int val = SND_NODE_TYPE_HW; + + if (!node || !node->card_node || !node->dev_node) + return SND_NODE_TYPE_HW; + + node->ops->get_int(node->dev_node, "type", &val); + + return val; +}; + +static int snd_utils_resolve_symbols(struct snd_node *node) +{ + void *dl = node->dl_hdl; + int err; + SND_DLSYM(dl, node->ops, "snd_card_ops", err); + return err; +} + +static struct snd_node *snd_utils_open_dev_node(unsigned int card, + unsigned int device, + int dev_type) +{ + struct snd_node *node; + int rc = 0; + + node = calloc(1, sizeof(*node)); + if (!node) + return NULL; + + node->dl_hdl = dlopen("libsndcardparser.so", RTLD_NOW); + if (!node->dl_hdl) { + goto err_dl_open; + } + + rc = snd_utils_resolve_symbols(node); + if (rc < 0) + goto err_resolve_symbols; + + node->card_node = node->ops->open_card(card); + if (!node->card_node) + goto err_resolve_symbols; + + if (dev_type == NODE_PCM) { + node->dev_node = node->ops->get_pcm(node->card_node, device); + } else { + node->dev_node = node->ops->get_mixer(node->card_node); + } + + if (!node->dev_node) + goto err_get_node; + + return node; + +err_get_node: + node->ops->close_card(node->card_node); + +err_resolve_symbols: + dlclose(node->dl_hdl); + +err_dl_open: + free(node); + return NULL; +} + +struct snd_node* snd_utils_open_pcm(unsigned int card, + unsigned int device) +{ + return snd_utils_open_dev_node(card, device, NODE_PCM); +} + +struct snd_node* snd_utils_open_mixer(unsigned int card) +{ + return snd_utils_open_dev_node(card, 0, NODE_MIXER); +} diff --git a/src/snd_card_plugin.h b/src/snd_card_plugin.h new file mode 100644 index 0000000..b80695e --- /dev/null +++ b/src/snd_card_plugin.h @@ -0,0 +1,74 @@ +/* snd_utils.h +** Copyright (c) 2019, The Linux Foundation. +** +** 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 TINYALSA_SRC_SND_CARD_UTILS_H +#define TINYALSA_SRC_SND_CARD_UTILS_H + +#include + +#include + +/** Encapsulates the pcm device definition from + * the sound card definition configuration file. + */ +struct snd_node { + /** Pointer the card definition */ + void *card_node; + /** Pointer to device definition, either PCM or MIXER device */ + void *dev_node; + /** Pointer to the sound card parser library */ + void *dl_hdl; + /** A pointer to the operations structure. */ + const struct snd_node_ops* ops; +}; + +enum snd_node_type { + SND_NODE_TYPE_HW = 0, + SND_NODE_TYPE_PLUGIN, + SND_NODE_TYPE_INVALID, +}; + +enum { + NODE_PCM, + NODE_MIXER +}; + +struct snd_node *snd_utils_open_pcm(unsigned int card, unsigned int device); + +struct snd_node *snd_utils_open_mixer(unsigned int card); + +void snd_utils_close_dev_node(struct snd_node *node); + +enum snd_node_type snd_utils_get_node_type(struct snd_node *node); + +int snd_utils_get_int(struct snd_node *node, const char *prop, int *val); + +int snd_utils_get_str(struct snd_node *node, const char *prop, char **val); + +#endif /* end of TINYALSA_SRC_SND_CARD_UTILS_H */ -- cgit v1.2.3 From e7c627dd74f5d43439792491c291564e298cbb10 Mon Sep 17 00:00:00 2001 From: Bhalchandra Gajare Date: Wed, 19 Jun 2019 15:30:42 -0700 Subject: tinyalsa: add support for mixer plugins Update the mixer framework to support plugins. Add ability for physical card to have either kernel registered mixer controls or plugin registered mixer control or both. Split mixer controls into two groups, one for kernel registered (hw_grp) and the other for plugin registered (virtual_grp). Signed-off-by: Rohit kumar Signed-off-by: Bhalchandra Gajare --- src/Makefile | 6 +- src/mixer.c | 392 ++++++++++++++++++++++++++++++++++--------- src/mixer_hw.c | 112 +++++++++++++ src/mixer_io.h | 51 ++++++ src/mixer_plugin.c | 480 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 966 insertions(+), 75 deletions(-) create mode 100644 src/mixer_hw.c create mode 100644 src/mixer_io.h create mode 100644 src/mixer_plugin.c (limited to 'src') diff --git a/src/Makefile b/src/Makefile index b278f47..79628b5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -15,7 +15,7 @@ INCLUDE_DIRS = -I ../include override CFLAGS := $(WARNINGS) $(INCLUDE_DIRS) -fPIC $(CFLAGS) VPATH = ../include/tinyalsa -OBJECTS = limits.o mixer.o pcm.o pcm_plugin.o pcm_hw.o snd_card_plugin.o +OBJECTS = limits.o mixer.o pcm.o pcm_plugin.o pcm_hw.o snd_card_plugin.o mixer_plugin.o mixer_hw.o LIBVERSION_MAJOR = $(TINYALSA_VERSION_MAJOR) LIBVERSION = $(TINYALSA_VERSION) @@ -35,6 +35,10 @@ mixer.o: mixer.c mixer.h snd_card_plugin.o: snd_card_plugin.c snd_card_plugin.h +mixer_plugin.o: mixer_plugin.c mixer_io.h + +mixer_hw.o: mixer_hw.c mixer_io.h + libtinyalsa.a: $(OBJECTS) $(AR) $(ARFLAGS) $@ $^ diff --git a/src/mixer.c b/src/mixer.c index e496e18..1c9e63b 100644 --- a/src/mixer.c +++ b/src/mixer.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,9 @@ #include #include +#include + +#include "mixer_io.h" /** A mixer control. * @ingroup libtinyalsa-mixer @@ -68,6 +72,21 @@ struct mixer_ctl { struct snd_ctl_elem_info info; /** A list of string representations of enumerated values (only valid for enumerated controls) */ char **ename; + /** Pointer to the group that the control belongs to */ + struct mixer_ctl_group *grp; +}; + +struct mixer_ctl_group { + /** A continuous array of mixer controls */ + struct mixer_ctl *ctl; + /** The number of mixer controls */ + unsigned int count; + /** The number of events associated with this group */ + unsigned int event_cnt; + /** The operations corresponding to this group */ + const struct mixer_ops *ops; + /** Private data for storing group specific data */ + void *data; }; /** A mixer handle. @@ -78,10 +97,15 @@ struct mixer { int fd; /** Card information */ struct snd_ctl_card_info card_info; - /** A continuous array of mixer controls */ - struct mixer_ctl *ctl; - /** The number of mixer controls */ - unsigned int count; + /* Hardware (kernel interface) mixer control group */ + struct mixer_ctl_group *h_grp; + /* Virtual (Plugin interface) mixer control group */ + struct mixer_ctl_group *v_grp; + /* Total count of mixer controls from both groups */ + unsigned int total_count; + /* Flag to track if card information is already retrieved */ + bool is_card_info_retrieved; + }; static void mixer_cleanup_control(struct mixer_ctl *ctl) @@ -96,25 +120,40 @@ static void mixer_cleanup_control(struct mixer_ctl *ctl) } } +static void mixer_grp_close(struct mixer *mixer, struct mixer_ctl_group *grp) +{ + unsigned int n; + + if (!grp) + return; + + if (grp->ctl) { + for (n = 0; n < grp->count; n++) + mixer_cleanup_control(&grp->ctl[n]); + free(grp->ctl); + } + + mixer->is_card_info_retrieved = false; +} + /** Closes a mixer returned by @ref mixer_open. * @param mixer A mixer handle. * @ingroup libtinyalsa-mixer */ void mixer_close(struct mixer *mixer) { - unsigned int n; - if (!mixer) return; - if (mixer->fd >= 0) - close(mixer->fd); + if (mixer->fd >= 0 && mixer->h_grp) + mixer->h_grp->ops->close(mixer->h_grp->data); + mixer_grp_close(mixer, mixer->h_grp); - if (mixer->ctl) { - for (n = 0; n < mixer->count; n++) - mixer_cleanup_control(&mixer->ctl[n]); - free(mixer->ctl); - } +#ifdef TINYALSA_USES_PLUGINS + if (mixer->v_grp) + mixer->v_grp->ops->close(mixer->v_grp->data); + mixer_grp_close(mixer, mixer->v_grp); +#endif free(mixer); @@ -133,18 +172,17 @@ static void *mixer_realloc_z(void *ptr, size_t curnum, size_t newnum, size_t siz return newp; } -static int add_controls(struct mixer *mixer) +static int add_controls(struct mixer *mixer, struct mixer_ctl_group *grp) { struct snd_ctl_elem_list elist; struct snd_ctl_elem_id *eid = NULL; struct mixer_ctl *ctl; - int fd = mixer->fd; - const unsigned int old_count = mixer->count; + const unsigned int old_count = grp->count; unsigned int new_count; unsigned int n; memset(&elist, 0, sizeof(elist)); - if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) + if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) goto fail; if (old_count == elist.count) @@ -153,12 +191,12 @@ static int add_controls(struct mixer *mixer) if (old_count > elist.count) return -1; /* driver has removed controls - this is bad */ - ctl = mixer_realloc_z(mixer->ctl, old_count, elist.count, + ctl = mixer_realloc_z(grp->ctl, old_count, elist.count, sizeof(struct mixer_ctl)); if (!ctl) goto fail; - mixer->ctl = ctl; + grp->ctl = ctl; /* ALSA drivers are not supposed to remove or re-order controls that * have already been created so we know that any new controls must @@ -174,18 +212,21 @@ static int add_controls(struct mixer *mixer) elist.pids = eid; - if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) + if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0) goto fail; for (n = old_count; n < new_count; n++) { - struct snd_ctl_elem_info *ei = &mixer->ctl[n].info; + struct snd_ctl_elem_info *ei = &grp->ctl[n].info; ei->id.numid = eid[n - old_count].numid; - if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0) + if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0) goto fail_extend; ctl[n].mixer = mixer; + ctl[n].grp = grp; } - mixer->count = new_count; + grp->count = new_count; + mixer->total_count += (new_count - old_count); + free(eid); return 0; @@ -196,13 +237,69 @@ fail_extend: */ mixer_cleanup_control(&ctl[n]); - mixer->count = n; /* keep controls we successfully added */ + grp->count = n; /* keep controls we successfully added */ + mixer->total_count += (n - old_count); /* fall through... */ fail: free(eid); return -1; } +static int mixer_grp_open(struct mixer *mixer, unsigned int card, bool is_hw) +{ + struct mixer_ctl_group *grp = NULL; + const struct mixer_ops *ops = NULL; + void *data = NULL; + int fd, ret; + + grp = calloc(1, sizeof(*grp)); + if (!grp) + return -ENOMEM; + + if (is_hw) { + mixer->fd = -1; + fd = mixer_hw_open(card, &data, &ops); + if (fd < 0) { + ret = fd; + goto err_open; + } + mixer->fd = fd; + mixer->h_grp = grp; + } +#ifdef TINYALSA_USES_PLUGINS + else { + ret = mixer_plugin_open(card, &data, &ops); + if (ret < 0) + goto err_open; + mixer->v_grp = grp; + } +#endif + grp->ops = ops; + grp->data = data; + + if (!mixer->is_card_info_retrieved) { + ret = grp->ops->ioctl(data, SNDRV_CTL_IOCTL_CARD_INFO, + &mixer->card_info); + if (ret < 0) + goto err_card_info; + mixer->is_card_info_retrieved = true; + } + + ret = add_controls(mixer, grp); + if (ret < 0) + goto err_card_info; + + return 0; + +err_card_info: + grp->ops->close(grp->data); + +err_open: + free(grp); + return ret; + +} + /** Opens a mixer for a given card. * @param card The card to open the mixer for. * @returns An initialized mixer handle. @@ -211,24 +308,20 @@ fail: struct mixer *mixer_open(unsigned int card) { struct mixer *mixer = NULL; - int fd; - char fn[256]; - - snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card); - fd = open(fn, O_RDWR); - if (fd < 0) - return 0; + int h_status, v_status = -1; mixer = calloc(1, sizeof(*mixer)); if (!mixer) goto fail; - if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0) - goto fail; + h_status = mixer_grp_open(mixer, card, true); - mixer->fd = fd; +#ifdef TINYALSA_USES_PLUGINS + v_status = mixer_grp_open(mixer, card, false); +#endif - if (add_controls(mixer) != 0) + /* both hw and virtual should fail for mixer_open to fail */ + if (h_status < 0 && v_status < 0) goto fail; return mixer; @@ -236,8 +329,7 @@ struct mixer *mixer_open(unsigned int card) fail: if (mixer) mixer_close(mixer); - else if (fd >= 0) - close(fd); + return NULL; } @@ -260,10 +352,27 @@ fail: */ int mixer_add_new_ctls(struct mixer *mixer) { + int rc1 = 0, rc2 = 0; + if (!mixer) return 0; - return add_controls(mixer); + /* add the h_grp controls */ + if (mixer->h_grp) + rc1 = add_controls(mixer, mixer->h_grp); + +#ifdef TINYALSA_USES_PLUGINS + /* add the v_grp controls */ + if (mixer->v_grp) + rc2 = add_controls(mixer, mixer->v_grp); +#endif + + if (rc1 < 0) + return rc1; + if (rc2 < 0) + return rc2; + + return 0; } /** Gets the name of the mixer's card. @@ -286,7 +395,7 @@ unsigned int mixer_get_num_ctls(const struct mixer *mixer) if (!mixer) return 0; - return mixer->count; + return mixer->total_count; } /** Gets the number of mixer controls, that go by a specified name, for a given mixer. @@ -297,6 +406,7 @@ unsigned int mixer_get_num_ctls(const struct mixer *mixer) */ unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *name) { + struct mixer_ctl_group *grp; unsigned int n; unsigned int count = 0; struct mixer_ctl *ctl; @@ -304,11 +414,24 @@ unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *n if (!mixer) return 0; - ctl = mixer->ctl; + if (mixer->h_grp) { + grp = mixer->h_grp; + ctl = grp->ctl; - for (n = 0; n < mixer->count; n++) - if (!strcmp(name, (char*) ctl[n].info.id.name)) - count++; + for (n = 0; n < grp->count; n++) + if (!strcmp(name, (char*) ctl[n].info.id.name)) + count++; + } +#ifdef TINYALSA_USES_PLUGINS + if (mixer->v_grp) { + grp = mixer->v_grp; + ctl = grp->ctl; + + for (n = 0; n < grp->count; n++) + if (!strcmp(name, (char*) ctl[n].info.id.name)) + count++; + } +#endif return count; } @@ -322,9 +445,21 @@ unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *n */ 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->h_grp) { + grp = mixer->h_grp; + if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) + return -1; + } + +#ifdef TINYALSA_USES_PLUGINS + if (mixer->v_grp) { + grp = mixer->v_grp; + if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) + return -1; } +#endif return 0; } @@ -338,25 +473,76 @@ int mixer_subscribe_events(struct mixer *mixer, int subscribe) */ int mixer_wait_event(struct mixer *mixer, int timeout) { - struct pollfd pfd; + struct pollfd *pfd; + struct mixer_ctl_group *grp; + int count = 0, num_fds = 0, i; - pfd.fd = mixer->fd; - pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL; + if (mixer->fd >= 0) + num_fds++; + +#ifdef TINYALSA_USES_PLUGINS + if (mixer->v_grp) + num_fds++; +#endif + + pfd = (struct pollfd *)calloc(sizeof(struct pollfd), num_fds); + if (!pfd) + return -ENOMEM; + + if (mixer->fd >= 0) { + pfd[count].fd = mixer->fd; + pfd[count].events = POLLIN | POLLOUT | POLLERR | POLLNVAL; + count++; + } + +#ifdef TINYALSA_USES_PLUGINS + if (mixer->v_grp) { + grp = mixer->v_grp; + if (!grp->ops->get_poll_fd(grp->data, pfd, count)) { + pfd[count].events = POLLIN | POLLERR | POLLNVAL; + count++; + } + } +#endif + + 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->h_grp; + grp->event_cnt++; + } +#ifdef TINYALSA_USES_PLUGINS + else { + grp = mixer->v_grp; + grp->event_cnt++; + } +#endif + return 1; + } + } } } +static unsigned int mixer_grp_get_count(struct mixer_ctl_group *grp) +{ + if (!grp) + return 0; + + return grp->count; +} + /** Gets a mixer control handle, by the mixer control's id. * For non-const access, see @ref mixer_get_ctl * @param mixer An initialized mixer handle. @@ -366,8 +552,22 @@ int mixer_wait_event(struct mixer *mixer, int timeout) */ const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned int id) { - if (mixer && (id < mixer->count)) - return mixer->ctl + id; + unsigned int h_count; + + if (!mixer || (id >= mixer->total_count)) + return NULL; + + h_count = mixer_grp_get_count(mixer->h_grp); + + if (id < h_count) + return mixer->h_grp->ctl + id; +#ifdef TINYALSA_USES_PLUGINS + else { + unsigned int v_count = mixer_grp_get_count(mixer->v_grp); + if ((id - h_count) < v_count) + return mixer->v_grp->ctl + (id - h_count); + } +#endif return NULL; } @@ -381,9 +581,22 @@ const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned */ struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id) { - if (mixer && (id < mixer->count)) - return mixer->ctl + id; + unsigned int h_count; + + if (!mixer || (id >= mixer->total_count)) + return NULL; + + h_count = mixer_grp_get_count(mixer->h_grp); + if (id < h_count) + return mixer->h_grp->ctl + id; +#ifdef TINYALSA_USES_PLUGINS + else { + unsigned int v_count = mixer_grp_get_count(mixer->v_grp); + if ((id - h_count) < v_count) + return mixer->v_grp->ctl + (id - h_count); + } +#endif return NULL; } @@ -410,19 +623,34 @@ struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer, const char *name, unsigned int index) { + struct mixer_ctl_group *grp; unsigned int n; struct mixer_ctl *ctl; if (!mixer) return NULL; - ctl = mixer->ctl; + if (mixer->h_grp) { + grp = mixer->h_grp; + ctl = grp->ctl; - for (n = 0; n < mixer->count; n++) - if (!strcmp(name, (char*) ctl[n].info.id.name)) - if (index-- == 0) - return mixer->ctl + n; + for (n = 0; n < grp->count; n++) + if (!strcmp(name, (char*) ctl[n].info.id.name)) + if (index-- == 0) + return ctl + n; + } + +#ifdef TINYALSA_USES_PLUGINS + if (mixer->v_grp) { + grp = mixer->v_grp; + ctl = grp->ctl; + for (n = 0; n < grp->count; n++) + if (!strcmp(name, (char*) ctl[n].info.id.name)) + if (index-- == 0) + return ctl + n; + } +#endif return NULL; } @@ -433,7 +661,13 @@ struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer, */ 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) + return; + + grp = ctl->grp; + grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &ctl->info); } /** Checks the control for TLV Read/Write access. @@ -595,15 +829,17 @@ int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent) */ int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id) { + struct mixer_ctl_group *grp; struct snd_ctl_elem_value ev; int ret; if (!ctl || (id >= ctl->info.count)) return -EINVAL; + grp = ctl->grp; 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; @@ -641,6 +877,7 @@ int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id) */ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count) { + struct mixer_ctl_group *grp; struct snd_ctl_elem_value ev; int ret = 0; size_t size; @@ -650,6 +887,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count) if (!ctl || !count || !array) return -EINVAL; + grp = ctl->grp; total_count = ctl->info.count; if ((ctl->info.type == SNDRV_CTL_ELEM_TYPE_BYTES) && @@ -667,7 +905,7 @@ int mixer_ctl_get_array(const 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]); @@ -687,7 +925,7 @@ int mixer_ctl_get_array(const 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); @@ -696,7 +934,7 @@ int mixer_ctl_get_array(const 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]); @@ -725,15 +963,17 @@ int mixer_ctl_get_array(const 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; struct snd_ctl_elem_value ev; int ret; if (!ctl || (id >= ctl->info.count)) return -EINVAL; + grp = ctl->grp; 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; @@ -763,7 +1003,7 @@ 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); } /** Sets the contents of a control's value array. @@ -778,6 +1018,7 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value) */ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) { + struct mixer_ctl_group *grp; struct snd_ctl_elem_value ev; size_t size; void *dest; @@ -786,6 +1027,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count) if ((!ctl) || !count || !array) return -EINVAL; + grp = ctl->grp; total_count = ctl->info.count; if ((ctl->info.type == SNDRV_CTL_ELEM_TYPE_BYTES) && @@ -821,7 +1063,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; @@ -837,7 +1079,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); } /** Gets the minimum value of an control. @@ -887,6 +1129,7 @@ unsigned int mixer_ctl_get_num_enums(const struct mixer_ctl *ctl) int mixer_ctl_fill_enum_string(struct mixer_ctl *ctl) { + struct mixer_ctl_group *grp = ctl->grp; struct snd_ctl_elem_info tmp; unsigned int m; char **enames; @@ -902,7 +1145,7 @@ int mixer_ctl_fill_enum_string(struct mixer_ctl *ctl) memset(&tmp, 0, sizeof(tmp)); tmp.id.numid = ctl->info.id.numid; tmp.value.enumerated.item = m; - 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[m] = strdup(tmp.value.enumerated.name); if (!enames[m]) @@ -949,6 +1192,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; unsigned int i, num_enums; struct snd_ctl_elem_value ev; int ret; @@ -957,13 +1201,14 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string) mixer_ctl_fill_enum_string(ctl) != 0) return -EINVAL; + grp = ctl->grp; num_enums = ctl->info.value.enumerated.items; for (i = 0; i < num_enums; i++) { if (!strcmp(string, ctl->ename[i])) { 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; @@ -972,4 +1217,3 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string) return -EINVAL; } - diff --git a/src/mixer_hw.c b/src/mixer_hw.c new file mode 100644 index 0000000..2e86dfa --- /dev/null +++ b/src/mixer_hw.c @@ -0,0 +1,112 @@ +/* mixer_hw.c +** Copyright (c) 2019, The Linux Foundation. +** +** 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 "mixer_io.h" + +/** Store the hardware (kernel interface) mixer data */ +struct mixer_hw_data { + /* Card number for the mixer */ + unsigned int card; + /* File descriptor of the mixer device node */ + 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 const struct mixer_ops mixer_hw_ops = { + .close = mixer_hw_close, + .ioctl = mixer_hw_ioctl, +}; + +int mixer_hw_open(unsigned int card, void **data, + const 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) + return -1; + + hw_data->card = card; + hw_data->fd = fd; + *data = hw_data; + *ops = &mixer_hw_ops; + + return fd; +} diff --git a/src/mixer_io.h b/src/mixer_io.h new file mode 100644 index 0000000..bb3bc44 --- /dev/null +++ b/src/mixer_io.h @@ -0,0 +1,51 @@ +/* mixer_io.h +** Copyright (c) 2019, The Linux Foundation. +** +** 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 TINYALSA_SRC_MIXER_H +#define TINYALSA_SRC_MIXER_H + +#include +#include +#include + +struct mixer_ops; + +int mixer_hw_open(unsigned int card, void **data, + const struct mixer_ops **ops); +int mixer_plugin_open(unsigned int card, void **data, + const 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 /* TINYALSA_SRC_MIXER_H */ diff --git a/src/mixer_plugin.c b/src/mixer_plugin.c new file mode 100644 index 0000000..3437171 --- /dev/null +++ b/src/mixer_plugin.c @@ -0,0 +1,480 @@ +/* mixer_plugin.c +** Copyright (c) 2019, The Linux Foundation. +** +** 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_card_plugin.h" +#include "mixer_io.h" + +/** Encapulates the mixer plugin specific data */ +struct mixer_plug_data { + /** Card number associated with the plugin */ + int card; + /** Device node for mixer */ + void *mixer_node; + /** Pointer to the plugin's ops */ + const struct mixer_plugin_ops *ops; + /** Pointer to plugin responsible to service the controls */ + struct mixer_plugin *plugin; + /** Handle to the plugin library */ + void *dl_hdl; +}; + +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) { + fprintf(stderr, "%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 = plug_data->ops->read_event(plugin, 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) { + plug_data->ops->subscribe_events(plugin, &mixer_plug_notifier_cb); + } else if (plugin->subscribed && !*subscribe) { + plug_data->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: + fprintf(stderr,"%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)); + + 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); + + plug_data->ops->close(&plugin); + dlclose(plug_data->dl_hdl); + + 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 const struct mixer_ops mixer_plug_ops = { + .close = mixer_plug_close, + .read_event = mixer_plug_read_event, + .get_poll_fd = mixer_plug_get_poll_fd, + .ioctl = mixer_plug_ioctl, +}; + +int mixer_plugin_open(unsigned int card, void **data, + const struct mixer_ops **ops) +{ + struct mixer_plug_data *plug_data; + struct mixer_plugin *plugin = NULL; + void *dl_hdl; + char *so_name; + int ret; + + plug_data = calloc(1, sizeof(*plug_data)); + if (!plug_data) + return -ENOMEM; + + plug_data->mixer_node = snd_utils_open_mixer(card); + 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_lib_name; + + } + + dl_hdl = dlopen(so_name, RTLD_NOW); + if (!dl_hdl) { + fprintf(stderr, "%s: unable to open %s\n", + __func__, so_name); + goto err_dlopen; + } + + dlerror(); + plug_data->ops = dlsym(dl_hdl, "mixer_plugin_ops"); + if (!plug_data->ops) { + fprintf(stderr, "%s: dlsym open fn failed: %s\n", + __func__, dlerror()); + goto err_ops; + } + + ret = plug_data->ops->open(&plugin, card); + if (ret) { + fprintf(stderr, "%s: failed to open plugin, err: %d\n", + __func__, ret); + goto err_ops; + } + + 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; + + return 0; + +err_ops: + dlclose(dl_hdl); +err_dlopen: +err_get_lib_name: + snd_utils_close_dev_node(plug_data->mixer_node); +err_get_mixer_node: + free(plug_data); + return -1; +} -- cgit v1.2.3