mirror of https://github.com/thesofproject/sof.git
453 lines
12 KiB
C
453 lines
12 KiB
C
/*-*- linux-c -*-*/
|
|
|
|
/*
|
|
* ALSA <-> SOF PCM I/O plugin
|
|
*
|
|
* Copyright(c) 2022 Intel Corporation. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as
|
|
* published by the Free Software Foundation; either version 2.1 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <sys/poll.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <mqueue.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <semaphore.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <alsa/asoundlib.h>
|
|
#include <alsa/pcm_external.h>
|
|
|
|
#include "plugin.h"
|
|
#include "common.h"
|
|
|
|
int plug_mq_cmd_tx_rx(struct plug_mq_desc *ipc_tx, struct plug_mq_desc *ipc_rx,
|
|
void *msg, size_t len, void *reply, size_t rlen)
|
|
{
|
|
struct timespec ts;
|
|
ssize_t ipc_size;
|
|
char mailbox[IPC3_MAX_MSG_SIZE];
|
|
int err;
|
|
|
|
if (len > IPC3_MAX_MSG_SIZE) {
|
|
SNDERR("ipc: message too big %d\n", len);
|
|
return -EINVAL;
|
|
}
|
|
memset(mailbox, 0, IPC3_MAX_MSG_SIZE);
|
|
memcpy(mailbox, msg, len);
|
|
|
|
/* wait for sof-pipe reader to consume data or timeout */
|
|
err = clock_gettime(CLOCK_REALTIME, &ts);
|
|
if (err == -1) {
|
|
SNDERR("ipc: cant get time: %s", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
/* IPCs should be read under 10ms */
|
|
plug_timespec_add_ms(&ts, 10);
|
|
|
|
/* now return message completion status */
|
|
err = mq_timedsend(ipc_tx->mq, mailbox, IPC3_MAX_MSG_SIZE, 0, &ts);
|
|
if (err == -1) {
|
|
SNDERR("error: timeout can't send IPC message queue %s : %s\n",
|
|
ipc_tx->queue_name, strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
/* wait for sof-pipe reader to consume data or timeout */
|
|
err = clock_gettime(CLOCK_REALTIME, &ts);
|
|
if (err == -1) {
|
|
SNDERR("ipc: cant get time: %s", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
/* IPCs should be processed under 20ms, but wait longer as
|
|
* some can take longer especially in valgrind
|
|
*/
|
|
plug_timespec_add_ms(&ts, 20);
|
|
|
|
ipc_size = mq_timedreceive(ipc_rx->mq, mailbox, IPC3_MAX_MSG_SIZE, NULL, &ts);
|
|
if (ipc_size == -1) {
|
|
//fprintf(stderr, "dbg: timeout can't read IPC message queue %s : %s retrying\n",
|
|
// ipc->queue_name, strerror(errno));
|
|
|
|
/* ok, its a long IPC or valgrind, wait longer */
|
|
plug_timespec_add_ms(&ts, 800);
|
|
|
|
ipc_size = mq_timedreceive(ipc_rx->mq, mailbox, IPC3_MAX_MSG_SIZE, NULL, &ts);
|
|
if (ipc_size == -1) {
|
|
SNDERR("error: timeout can't read IPC message queue %s : %s\n",
|
|
ipc_rx->queue_name, strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
/* needed for valgrind to complete MQ op before next client IPC */
|
|
ts.tv_nsec = 20 * 1000 * 1000;
|
|
ts.tv_sec = 0;
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
|
|
/* do the message work */
|
|
if (rlen && reply)
|
|
memcpy(reply, mailbox, rlen);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int plug_mq_cmd(struct plug_mq_desc *ipc, void *msg, size_t len, void *reply, size_t rlen)
|
|
{
|
|
return plug_mq_cmd_tx_rx(ipc, ipc, msg, len, reply, rlen);
|
|
}
|
|
|
|
/*
|
|
* Open an existing message queue using IPC object.
|
|
*/
|
|
int plug_mq_open(struct plug_mq_desc *ipc)
|
|
{
|
|
/* now open new queue for Tx and Rx */
|
|
ipc->mq = mq_open(ipc->queue_name, O_RDWR);
|
|
if (ipc->mq < 0) {
|
|
// SNDERR("failed to open IPC queue %s: %s\n",
|
|
// ipc->queue_name, strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Open an existing semaphore using lock object.
|
|
*/
|
|
int plug_lock_open(struct plug_sem_desc *lock)
|
|
{
|
|
lock->sem = sem_open(lock->name, O_RDWR);
|
|
if (lock->sem == SEM_FAILED) {
|
|
SNDERR("failed to open semaphore %s: %s\n", lock->name, strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define itemsize(type, member) sizeof(((type *)0)->member)
|
|
|
|
static int parse_conf_long(snd_config_t *cfg, void *obj, size_t size)
|
|
{
|
|
long val;
|
|
|
|
if (snd_config_get_integer(cfg, &val) < 0)
|
|
return -EINVAL;
|
|
|
|
*((long *)obj) = val;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_conf_str(snd_config_t *cfg, void *obj, size_t size)
|
|
{
|
|
const char *id;
|
|
|
|
if (snd_config_get_id(cfg, &id) < 0)
|
|
return -EINVAL;
|
|
strncpy(obj, id, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_conf_format(snd_config_t *cfg, void *obj, size_t size)
|
|
{
|
|
const char *id;
|
|
|
|
if (snd_config_get_string(cfg, &id) < 0)
|
|
return -EINVAL;
|
|
|
|
if (!strcmp("S16_LE", id)) {
|
|
*((long *)obj) = SND_PCM_FORMAT_S16_LE;
|
|
return 0;
|
|
}
|
|
if (!strcmp("S32_LE", id)) {
|
|
*((long *)obj) = SND_PCM_FORMAT_S32_LE;
|
|
return 0;
|
|
}
|
|
if (!strcmp("S24_4LE", id)) {
|
|
*((long *)obj) = SND_PCM_FORMAT_S24_LE;
|
|
return 0;
|
|
}
|
|
if (!strcmp("FLOAT", id)) {
|
|
*((long *)obj) = SND_PCM_FORMAT_FLOAT_LE;
|
|
return 0;
|
|
}
|
|
|
|
/* not found */
|
|
SNDERR("error: cant find format: %s", id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
struct config_item {
|
|
char *name;
|
|
size_t size;
|
|
size_t offset;
|
|
int (*copy)(snd_config_t *cfg, void *obj, size_t size);
|
|
};
|
|
|
|
struct config_item config_items[] = {
|
|
{"name", itemsize(struct plug_config, name),
|
|
offsetof(struct plug_config, name), parse_conf_str},
|
|
{"rate", itemsize(struct plug_config, rate),
|
|
offsetof(struct plug_config, rate), parse_conf_long},
|
|
{"format", itemsize(struct plug_config, format),
|
|
offsetof(struct plug_config, format), parse_conf_format},
|
|
{"channels", itemsize(struct plug_config, channels),
|
|
offsetof(struct plug_config, channels), parse_conf_long},
|
|
{"period_time", itemsize(struct plug_config, period_time),
|
|
offsetof(struct plug_config, period_time), parse_conf_long},
|
|
{"period_frames", itemsize(struct plug_config, period_frames),
|
|
offsetof(struct plug_config, period_frames), parse_conf_long},
|
|
{"buffer_time", itemsize(struct plug_config, buffer_time),
|
|
offsetof(struct plug_config, buffer_time), parse_conf_long},
|
|
{"buffer_frames", itemsize(struct plug_config, buffer_frames),
|
|
offsetof(struct plug_config, buffer_frames), parse_conf_long},
|
|
};
|
|
|
|
static int parse_item(snd_config_t *cfg, const char *id, struct plug_config *dest_cfg)
|
|
{
|
|
void *dest = dest_cfg;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(config_items); i++) {
|
|
/* does ID match */
|
|
if (strcmp(id, config_items[i].name))
|
|
continue;
|
|
|
|
/* now get the value */
|
|
return config_items[i].copy(cfg, dest + config_items[i].offset,
|
|
config_items[i].size);
|
|
}
|
|
|
|
/* not found - non fatal */
|
|
return 0;
|
|
}
|
|
|
|
static int parse_slave_configs(snd_sof_plug_t *plug, snd_config_t *n)
|
|
{
|
|
snd_config_iterator_t si1, si2, snext1, snext2;
|
|
struct plug_config *config;
|
|
const char *id;
|
|
|
|
fprintf(stdout, "Parsing ALSA conf for configs\n");
|
|
|
|
snd_config_for_each(si1, snext1, n) {
|
|
snd_config_t *sn1 = snd_config_iterator_entry(si1);
|
|
|
|
config = &plug->config[plug->num_configs];
|
|
|
|
/* get config name */
|
|
if (parse_item(sn1, "name", config) < 0) {
|
|
SNDERR("error: cant find config name");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* now get item values in each config */
|
|
snd_config_for_each(si2, snext2, sn1) {
|
|
snd_config_t *sn2 = snd_config_iterator_entry(si2);
|
|
|
|
if (snd_config_get_id(sn2, &id) < 0)
|
|
continue;
|
|
|
|
if (parse_item(sn2, id, config) < 0) {
|
|
SNDERR("error: malformed config: %s", id);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
fprintf(stdout, " config %d: %s\n", plug->num_configs,
|
|
config->name);
|
|
|
|
/* next config */
|
|
plug->num_configs++;
|
|
if (plug->num_configs >= PLUG_MAX_CONFIG) {
|
|
SNDERR("error: too many configs");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Parse the client cmdline. Format is
|
|
* tplg:pcm:card:dev:config[dai_pipe:card:dev:config]...]
|
|
*/
|
|
static int parse_client_cmdline(snd_sof_plug_t *plug, char *cmdline)
|
|
{
|
|
struct plug_cmdline_item *cmd_item;
|
|
char *tplg, *next, *card, *dev, *config, *pcm;
|
|
char *tplg_path = getenv("SOF_PLUGIN_TOPOLOGY_PATH");
|
|
char tplg_file[128];
|
|
int ret;
|
|
int i;
|
|
|
|
if (!tplg_path) {
|
|
SNDERR("Invalid topology path. Please set the SOF_PLUGIN_TOPOLOGY_PATH env variable\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* get topology file */
|
|
tplg = strtok_r(cmdline, ":", &next);
|
|
if (!tplg) {
|
|
SNDERR("invalid cmdline, cant find topology %s", cmdline);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* now convert to filename and add the topology path */
|
|
ret = snprintf(tplg_file, sizeof(tplg_file), "%ssof-%s.tplg", tplg_path, tplg);
|
|
if (ret < 0) {
|
|
SNDERR("invalid cmdline topology file %s", tplg);
|
|
return -EINVAL;
|
|
}
|
|
plug->tplg_file = strdup(tplg_file);
|
|
if (!plug->tplg_file)
|
|
return -ENOMEM;
|
|
|
|
/* get PCM ID */
|
|
pcm = strtok_r(next, ":", &next);
|
|
if (!pcm) {
|
|
SNDERR("invalid cmdline, cant find PCM %s", pcm);
|
|
return -EINVAL;
|
|
}
|
|
plug->pcm_id = atoi(pcm);
|
|
|
|
fprintf(stdout, "Parsing cmd line\n");
|
|
|
|
cmd_item = &plug->cmdline[plug->num_cmdline];
|
|
card = strtok_r(next, ":", &next);
|
|
if (!card) {
|
|
SNDERR("Invalid card name\n");
|
|
return -EINVAL;
|
|
}
|
|
dev = strtok_r(next, ":", &next);
|
|
if (!dev) {
|
|
SNDERR("Invalid dev name\n");
|
|
return -EINVAL;
|
|
}
|
|
config = strtok_r(next, ":", &next);
|
|
|
|
/* tuple needs all three, any missing ? */
|
|
if (!config) {
|
|
SNDERR("invalid cmdline, expected pcm(%s):card(%s):dev(%s):config(%s) from %s",
|
|
pcm, card, dev, config, tplg);
|
|
return -EINVAL;
|
|
}
|
|
|
|
cmd_item->pcm = atoi(pcm);
|
|
strncpy(cmd_item->card_name, card, sizeof(cmd_item->card_name));
|
|
strncpy(cmd_item->dev_name, dev, sizeof(cmd_item->dev_name));
|
|
strncpy(cmd_item->config_name, config, sizeof(cmd_item->config_name));
|
|
|
|
/*
|
|
* dev name is special, we cant use "," in the command line
|
|
* so need to replace it with a "." and then later change it
|
|
* back to ","
|
|
*/
|
|
for (i = 0; i < sizeof(cmd_item->dev_name); i++) {
|
|
if (cmd_item->dev_name[i] != '.')
|
|
continue;
|
|
cmd_item->dev_name[i] = ',';
|
|
break;
|
|
}
|
|
|
|
fprintf(stdout, " cmd %d: for pcm %d uses %s with PCM %s:%s\n",
|
|
plug->num_cmdline, cmd_item->pcm, cmd_item->config_name,
|
|
cmd_item->card_name, cmd_item->dev_name);
|
|
|
|
plug->num_cmdline++;
|
|
|
|
printf("plug: topology file %s with pipe %ld\n", plug->tplg_file, plug->tplg_pipeline);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Parse the ALSA conf for the SOF plugin and construct the command line options
|
|
* to be passed into the SOF pipe executable.
|
|
* TODO: verify all args
|
|
* TODO: validate all args.
|
|
* TODO: contruct sof pipe cmd line.
|
|
*/
|
|
int plug_parse_conf(snd_sof_plug_t *plug, const char *name, snd_config_t *root,
|
|
snd_config_t *conf)
|
|
{
|
|
snd_config_iterator_t i, next;
|
|
const char *tplg = NULL;
|
|
|
|
/*
|
|
* The topology filename and topology PCM need to be passed in.
|
|
* i.e. aplay -Dsof:tplg:pcm:[card:dev:config]...]
|
|
*/
|
|
snd_config_for_each(i, next, conf) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
const char *id;
|
|
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
continue;
|
|
|
|
/* dont care */
|
|
if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 ||
|
|
strcmp(id, "hint") == 0)
|
|
continue;
|
|
|
|
/* client command line topology */
|
|
if (strcmp(id, "tplg") == 0) {
|
|
if (snd_config_get_string(n, &tplg) < 0) {
|
|
SNDERR("Invalid type for %s", id);
|
|
return -EINVAL;
|
|
} else if (!*tplg) {
|
|
tplg = NULL;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* topology PCM configurations */
|
|
if (strcmp(id, "config") == 0) {
|
|
if (parse_slave_configs(plug, n))
|
|
return -EINVAL;
|
|
continue;
|
|
}
|
|
|
|
/* not fatal - carry on and verify later */
|
|
SNDERR("Unknown field %s", id);
|
|
}
|
|
|
|
/* verify mandatory inputs are specified */
|
|
if (!tplg) {
|
|
SNDERR("Missing topology topology");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* parse the client command line */
|
|
if (parse_client_cmdline(plug, (char *)tplg)) {
|
|
SNDERR("invalid sof cmd line");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|