You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
800 lines
22 KiB
800 lines
22 KiB
/* pcm_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 <fcntl.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <poll.h>
|
|
#include <dlfcn.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <linux/ioctl.h>
|
|
#include <sound/asound.h>
|
|
#include <tinyalsa/asoundlib.h>
|
|
#include <tinyalsa/pcm_plugin.h>
|
|
|
|
#include "pcm_io.h"
|
|
#include "snd_utils.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;
|
|
PCM_PLUGIN_OPEN_FN_PTR();
|
|
|
|
struct pcm_plugin *plugin;
|
|
void *dev_node;
|
|
};
|
|
|
|
static unsigned int my_params[] = {
|
|
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;
|
|
|
|
plugin->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(my_params); i++)
|
|
pcm_plug_interval_select(p, my_params[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 = plugin->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 plugin->ops->sw_params(plugin, params);
|
|
}
|
|
|
|
static int convert_plugin_to_pcm_state(int plugin_state)
|
|
{
|
|
switch (plugin_state) {
|
|
case PCM_PLUG_STATE_SETUP:
|
|
return PCM_STATE_SETUP;
|
|
case PCM_PLUG_STATE_RUNNING:
|
|
return PCM_STATE_RUNNING;
|
|
case PCM_PLUG_STATE_PREPARED:
|
|
return PCM_STATE_PREPARED;
|
|
case PCM_PLUG_STATE_OPEN:
|
|
return PCM_STATE_OPEN;
|
|
}
|
|
|
|
return PCM_STATE_OPEN;
|
|
}
|
|
|
|
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;
|
|
int ret = -EBADFD;
|
|
|
|
if (plugin->state >= PCM_PLUG_STATE_SETUP) {
|
|
ret = plugin->ops->sync_ptr(plugin, sync_ptr);
|
|
if (ret == 0)
|
|
sync_ptr->s.status.state = convert_plugin_to_pcm_state(plugin->state);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
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 plugin->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 plugin->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_SETUP)
|
|
return plugin->ops->ttstamp(plugin, tstamp);
|
|
else
|
|
return -EBADFD;
|
|
}
|
|
|
|
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 = plugin->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 = plugin->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 = 0;
|
|
|
|
rc = plugin->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 = plugin->ops->ioctl(plugin, cmd, arg);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int pcm_plug_poll(void *data, struct pollfd *pfd, nfds_t nfds,
|
|
int timeout)
|
|
{
|
|
struct pcm_plug_data *plug_data = data;
|
|
struct pcm_plugin *plugin = plug_data->plugin;
|
|
|
|
return plugin->ops->poll(plugin, pfd, nfds, timeout);
|
|
}
|
|
|
|
static void* pcm_plug_mmap(void *data, void *addr, size_t length, int prot,
|
|
int flags, off_t offset)
|
|
{
|
|
struct pcm_plug_data *plug_data = data;
|
|
struct pcm_plugin *plugin = plug_data->plugin;
|
|
|
|
if (plugin->state != PCM_PLUG_STATE_SETUP)
|
|
return NULL;
|
|
return plugin->ops->mmap(plugin, addr, length, prot, flags, offset);
|
|
}
|
|
|
|
static int pcm_plug_munmap(void *data, void *addr, size_t length)
|
|
{
|
|
struct pcm_plug_data *plug_data = data;
|
|
struct pcm_plugin *plugin = plug_data->plugin;
|
|
|
|
if (plugin->state != PCM_PLUG_STATE_SETUP)
|
|
return -EBADFD;
|
|
|
|
return plugin->ops->munmap(plugin, addr, length);
|
|
}
|
|
|
|
static int pcm_plug_open(unsigned int card, unsigned int device,
|
|
unsigned int flags, void **data, void *pcm_node)
|
|
{
|
|
struct pcm_plug_data *plug_data;
|
|
void *dl_hdl;
|
|
int rc = 0;
|
|
char *so_name, token[80], *name, *open_fn, *token_saveptr;
|
|
|
|
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: %s\n", __func__, so_name, dlerror());
|
|
goto err_dl_open;
|
|
} else {
|
|
fprintf(stderr, "%s: dlopen successful for %s\n", __func__, so_name);
|
|
}
|
|
|
|
dlerror();
|
|
|
|
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_open_fn;
|
|
}
|
|
open_fn = calloc(1, strlen(name) + strlen("_open") + 1);
|
|
if (!open_fn) {
|
|
rc = -ENOMEM;
|
|
goto err_open_fn;
|
|
}
|
|
|
|
strncpy(open_fn, name, strlen(name) + 1);
|
|
strcat(open_fn, "_open");
|
|
|
|
printf("%s - %s\n", __func__, open_fn);
|
|
plug_data->plugin_open_fn = dlsym(dl_hdl, open_fn);
|
|
if (!plug_data->plugin_open_fn) {
|
|
fprintf(stderr, "%s: dlsym to open fn failed, err = '%s'\n",
|
|
__func__, dlerror());
|
|
goto err_dlsym;
|
|
}
|
|
|
|
rc = plug_data->plugin_open_fn(&plug_data->plugin,
|
|
card, device, flags);
|
|
if (rc) {
|
|
fprintf(stderr, "%s: failed to open plugin\n", __func__);
|
|
goto err_dlsym;
|
|
}
|
|
|
|
/* Call snd-card-def to get card and pcm nodes */
|
|
/* Check how to manage fd for plugin */
|
|
|
|
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;
|
|
|
|
free(open_fn);
|
|
return 0;
|
|
|
|
err_dlsym:
|
|
free(open_fn);
|
|
err_open_fn:
|
|
dlclose(dl_hdl);
|
|
err_get_lib:
|
|
err_dl_open:
|
|
free(plug_data);
|
|
|
|
return rc;
|
|
}
|
|
|
|
struct pcm_ops plug_ops = {
|
|
.open = pcm_plug_open,
|
|
.close = pcm_plug_close,
|
|
.ioctl = pcm_plug_ioctl,
|
|
.mmap = pcm_plug_mmap,
|
|
.munmap = pcm_plug_munmap,
|
|
.poll = pcm_plug_poll,
|
|
};
|