blob: 525028da4a041b2638497e64d7f8a907ddeb60de [file] [log] [blame]
/*
** Copyright (c) 2019, 2021, 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 <errno.h>
#include <expat.h>
#include <pthread.h>
#include <snd-card-def.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <agm/agm_list.h>
#define MAX_PATH 256
#define BUF_SIZE 1024
struct snd_prop_val_pair {
char *prop;
char *val;
struct listnode list_node;
};
struct snd_dev_def {
enum snd_node_type node_type;
struct snd_dev_def_card *card;
/* Mandatory properties from XML */
unsigned int device;
int type;
char *name;
char *so_name;
struct listnode list_node;
/* List of custom properties */
struct listnode prop_val_list;
};
struct snd_dev_def_card {
unsigned int card;
int type;
char *name;
int refcnt;
struct listnode list_node;
/* child device details */
struct listnode pcm_devs_list;
struct listnode mixer_devs_list;
struct listnode compr_devs_list;
};
static struct listnode snd_card_list;
static bool snd_card_list_init = false;
static pthread_rwlock_t snd_rwlock = PTHREAD_RWLOCK_INITIALIZER;
typedef enum {
TAG_ROOT,
TAG_CARD,
TAG_DEVICE,
TAG_PLUGIN,
TAG_DEV_PROPS,
} snd_card_defs_xml_tags_t;
struct xml_userdata {
char data_buf[BUF_SIZE];
size_t offs;
unsigned int card;
char *card_name;
struct snd_dev_def_card *cur_card_def;
struct snd_dev_def *cur_dev_def;
bool card_found;
bool card_parsed;
snd_card_defs_xml_tags_t current_tag;
};
static void snd_process_data_buf(struct xml_userdata *data, const XML_Char *tag_name);
static void snd_dev_def_init(struct xml_userdata *data, const XML_Char *tag_name, enum snd_node_type node_type);
static void snd_reset_data_buf(struct xml_userdata *data)
{
data->offs = 0;
data->data_buf[data->offs] = '\0';
}
static void snd_dev_set_type(struct snd_dev_def *dev_def, int type)
{
if (dev_def)
dev_def->type = type;
}
static void snd_start_tag(void *userdata, const XML_Char *tag_name,
const XML_Char **attr)
{
struct xml_userdata *data = (struct xml_userdata *)userdata;
enum snd_node_type node_type = -1;
if (data->card_parsed)
return;
snd_reset_data_buf(data);
if (!strcmp(tag_name, "card"))
data->current_tag = TAG_CARD;
if (!strcmp(tag_name, "pcm-device")) {
data->current_tag = TAG_DEVICE;
node_type = SND_NODE_TYPE_PCM;
} else if (!strcmp(tag_name, "compress-device")) {
data->current_tag = TAG_DEVICE;
node_type = SND_NODE_TYPE_COMPR;
} else if (!strcmp(tag_name, "mixer")) {
data->current_tag = TAG_DEVICE;
node_type = SND_NODE_TYPE_MIXER;
} else if (strstr(tag_name, "plugin")) {
snd_dev_set_type(data->cur_dev_def, SND_NODE_DEV_TYPE_PLUGIN);
data->current_tag = TAG_PLUGIN;
} else if (!strcmp(tag_name, "props")) {
data->current_tag = TAG_DEV_PROPS;
}
if (data->current_tag != TAG_CARD && !data->card_found)
return;
snd_dev_def_init(data, tag_name, node_type);
}
static void snd_end_tag(void *userdata, const XML_Char *tag_name)
{
struct xml_userdata *data = (struct xml_userdata *)userdata;
if (data->card_parsed)
return;
if (data->current_tag != TAG_CARD && !data->card_found)
return;
snd_process_data_buf(data, tag_name);
snd_reset_data_buf(data);
if (!strcmp(tag_name, "mixer") || !strcmp(tag_name, "pcm-device") || !strcmp(tag_name, "compress-device"))
data->current_tag = TAG_CARD;
else if (strstr(tag_name, "plugin") || !strcmp(tag_name, "props"))
data->current_tag = TAG_DEVICE;
else if(!strcmp(tag_name, "card")) {
data->current_tag = TAG_ROOT;
if (data->card_found)
data->card_parsed = true;
}
}
static void snd_data_handler(void *userdata, const XML_Char *s, int len)
{
struct xml_userdata *data = (struct xml_userdata *)userdata;
if (len + data->offs >= sizeof(data->data_buf) ) {
data->offs += len;
/* string length overflow, return */
return;
} else {
memcpy(data->data_buf + data->offs, s, len);
data->offs += len;
}
}
static void snd_dev_def_init(struct xml_userdata *data, const XML_Char *tag_name, enum snd_node_type node_type)
{
struct snd_dev_def *dev_def = NULL;
struct snd_dev_def_card *card_def = NULL;
struct listnode *devs_list;
card_def = data->cur_card_def;
if (!card_def)
return;
if (node_type == SND_NODE_TYPE_PCM)
devs_list = &card_def->pcm_devs_list;
else if (node_type == SND_NODE_TYPE_COMPR)
devs_list = &card_def->compr_devs_list;
else if (node_type == SND_NODE_TYPE_MIXER)
devs_list = &card_def->mixer_devs_list;
else
return;
if (!(!strcmp(tag_name, "pcm-device") | !strcmp(tag_name, "compress-device") | !strcmp(tag_name, "mixer")))
return;
dev_def = calloc(1, sizeof(struct snd_dev_def));
if (!dev_def)
return;
dev_def->node_type = node_type;
dev_def->card = card_def;
dev_def->type = SND_NODE_DEV_TYPE_HW;
list_init(&dev_def->prop_val_list);
list_add_tail(devs_list, &dev_def->list_node);
data->cur_dev_def = dev_def;
}
static struct snd_dev_def_card *snd_parse_initialize_card_def(struct xml_userdata *data)
{
struct snd_dev_def_card *card_def = NULL;
if (data->cur_card_def)
return data->cur_card_def;
card_def = calloc(1, sizeof(struct snd_dev_def_card));
if (!card_def)
return card_def;
data->card_found = true;
data->cur_card_def = card_def;
list_init(&card_def->pcm_devs_list);
list_init(&card_def->mixer_devs_list);
list_init(&card_def->compr_devs_list);
return card_def;
}
static void snd_parse_card_properties(struct xml_userdata *data, const XML_Char *tag_name)
{
struct snd_dev_def_card *card_def = NULL;
unsigned int card;
const char s[4] = ", ";
char *token = NULL;
char *tok_ptr;
if (!strcmp(tag_name, "id")) {
card = atoi(data->data_buf);
if (card == data->card) {
card_def = snd_parse_initialize_card_def(data);
if (card_def)
card_def->card = card;
}
} else if (!strcmp(tag_name, "name")) {
card_def = data->cur_card_def;
if (!card_def) {
if (!data->card_name)
return;
token = strtok_r(data->data_buf, s, &tok_ptr);
while (token != 0) {
if (!strncmp(data->card_name, token, strlen(data->card_name))) {
card_def = snd_parse_initialize_card_def(data);
if (!card_def)
return;
goto card_found;
}
token = strtok_r(0, s, &tok_ptr);
}
return;
}
card_found:
if (token)
strlcpy(data->data_buf, token, strlen(token) + 1);
card_def->name = calloc(1, strlen(data->data_buf) + 1);
if (!card_def->name)
return;
strlcpy(card_def->name, data->data_buf, strlen(data->data_buf) + 1);
}
}
static void snd_parse_plugin_properties(struct xml_userdata *data, const XML_Char *tag_name)
{
struct snd_dev_def *dev_def = NULL;
dev_def = data->cur_dev_def;
if (!dev_def)
return;
if (!strcmp(tag_name, "so-name")) {
dev_def->so_name = calloc(1, strlen(data->data_buf) + 1);
if (!dev_def->so_name)
return;
strlcpy(dev_def->so_name, data->data_buf, strlen(data->data_buf) + 1);
}
}
static void snd_parse_device_properties(struct xml_userdata *data, const XML_Char *tag_name)
{
struct snd_dev_def *dev_def = NULL;
if (!strcmp(tag_name, "pcm-device") || !strcmp(tag_name, "compress-device") || !strcmp(tag_name, "mixer"))
return;
dev_def = data->cur_dev_def;
if (!dev_def)
return;
if (!strcmp(tag_name, "id")) {
dev_def->device = atoi(data->data_buf);
} else if (!strcmp(tag_name, "name")) {
dev_def->name = calloc(1, strlen(data->data_buf) + 1);
if (!dev_def->name)
return;
strlcpy(dev_def->name, data->data_buf, strlen(data->data_buf) + 1);
}
}
static void snd_parse_device_custom_properties(struct xml_userdata *data, const XML_Char *tag_name)
{
struct snd_dev_def *dev_def = NULL;
struct snd_prop_val_pair *pv_pair = NULL;
if (!strcmp(tag_name, "props"))
return;
dev_def = data->cur_dev_def;
if (!dev_def)
return;
/* parse custom properties */
if (!strlen(data->data_buf) || !strlen(tag_name))
return;
pv_pair = calloc(1, sizeof(struct snd_prop_val_pair));
if (!pv_pair)
return;
pv_pair->prop = calloc(1, strlen(tag_name) + 1);
if (!pv_pair->prop) {
free(pv_pair);
return;
}
strlcpy(pv_pair->prop, tag_name, strlen(tag_name) + 1);
pv_pair->val = calloc(1, strlen(data->data_buf) + 1);
if (!pv_pair->val) {
free(pv_pair->prop);
free(pv_pair);
return;
}
strlcpy(pv_pair->val, data->data_buf, strlen(data->data_buf) + 1);
list_add_tail(&dev_def->prop_val_list, &pv_pair->list_node);
}
static void snd_process_data_buf (struct xml_userdata *data, const XML_Char *tag_name)
{
if (data->offs <= 0)
return;
data->data_buf[data->offs] = '\0';
if (data->card_parsed)
return;
if (data->current_tag == TAG_ROOT)
return;
if (data->current_tag == TAG_CARD)
snd_parse_card_properties(data, tag_name);
else if (data->current_tag == TAG_PLUGIN)
snd_parse_plugin_properties(data, tag_name);
else if (data->current_tag == TAG_DEVICE)
snd_parse_device_properties(data, tag_name);
else
snd_parse_device_custom_properties(data, tag_name);
}
static void snd_free_card_devs_def(struct listnode *dev_list)
{
struct snd_dev_def *dev_def = NULL;
struct snd_prop_val_pair *pv_pair;
struct listnode *pv_pair_node, *dev_node, *temp, *temp2;
list_for_each_safe(dev_node, temp, dev_list) {
dev_def = node_to_item(dev_node, struct snd_dev_def, list_node);
list_remove(dev_node);
free(dev_def->name);
free(dev_def->so_name);
list_for_each_safe(pv_pair_node, temp2, &dev_def->prop_val_list) {
pv_pair = node_to_item(pv_pair_node, struct snd_prop_val_pair, list_node);
list_remove(pv_pair_node);
free(pv_pair->prop);
free(pv_pair->val);
free(pv_pair);
}
free(dev_def);
}
}
static void snd_free_card_def(struct snd_dev_def_card *card_def)
{
struct listnode *dev_list;
if (!card_def)
return;
dev_list = &card_def->pcm_devs_list;
snd_free_card_devs_def(dev_list);
dev_list = &card_def->compr_devs_list;
snd_free_card_devs_def(dev_list);
dev_list = &card_def->mixer_devs_list;
snd_free_card_devs_def(dev_list);
free(card_def->name);
free(card_def);
}
void *snd_card_def_get_card(unsigned int card)
{
FILE *file;
XML_Parser parser;
void *buf;
int bytes_read, len = 0;
char *snd_card_name = NULL;
bool card_found = false;
struct listnode *snd_card_node, *temp;
struct xml_userdata card_data;
struct snd_dev_def_card *card_def = NULL;
char filename[MAX_PATH];
snprintf(filename, MAX_PATH, "/proc/asound/card%d/id", card);
if (access(filename, F_OK ) != -1 ) {
file = fopen(filename, "r");
if (!file) {
printf("open %s: failed\n", filename);
} else {
snd_card_name = calloc(1, BUF_SIZE);
if (!snd_card_name)
return NULL;
if (fgets(snd_card_name, BUF_SIZE - 1, file)) {
len = strlen(snd_card_name);
snd_card_name[len - 1] = '\0';
card = UINT_MAX;
} else {
free(snd_card_name);
snd_card_name = NULL;
}
fclose(file);
}
}
file = NULL;
pthread_rwlock_wrlock(&snd_rwlock);
if (snd_card_list_init == false) {
list_init(&snd_card_list);
snd_card_list_init = true;
}
memset(&card_data, 0, sizeof(card_data));
list_for_each_safe(snd_card_node, temp, &snd_card_list) {
card_def = node_to_item(snd_card_node, struct snd_dev_def_card, list_node);
if (snd_card_name) {
if (!strncmp(snd_card_name, card_def->name, strlen(snd_card_name)))
card_found = true;
} else if (card_def->card == card) {
card_found = true;
}
if (card_found) {
card_def->refcnt++;
pthread_rwlock_unlock(&snd_rwlock);
free(snd_card_name);
return card_def;
}
}
/* read XML */
file = fopen(CARD_DEF_FILE, "r");
if (!file) {
pthread_rwlock_unlock(&snd_rwlock);
free(snd_card_name);
return NULL;
}
parser = XML_ParserCreate(NULL);
if (!parser) {
fclose(file);
pthread_rwlock_unlock(&snd_rwlock);
free(snd_card_name);
return NULL;
}
card_data.card = card;
card_data.card_name = snd_card_name;
XML_SetUserData(parser, &card_data);
XML_SetElementHandler(parser, snd_start_tag, snd_end_tag);
XML_SetCharacterDataHandler(parser, snd_data_handler);
/* parse XML */
for (;;) {
buf = XML_GetBuffer(parser, BUF_SIZE);
if (buf == NULL)
goto ret;
bytes_read = fread(buf, 1, BUF_SIZE, file);
if (bytes_read < 0)
goto ret;
if (XML_ParseBuffer(parser, bytes_read,
bytes_read == 0) == XML_STATUS_ERROR) {
goto ret;
}
if (bytes_read == 0)
break;
}
card_def = card_data.cur_card_def;
if (card_def) {
list_add_tail(&snd_card_list, &card_def->list_node);
card_def->refcnt++;
}
ret:
free(snd_card_name);
card_data.card_name = NULL;
XML_ParserFree(parser);
fclose(file);
pthread_rwlock_unlock(&snd_rwlock);
return card_def;
}
void snd_card_def_put_card(void *card_node)
{
struct snd_dev_def_card *defs = (struct snd_dev_def_card *)card_node;
struct snd_dev_def_card *card_def = NULL;
struct listnode *snd_card_node, *temp;
if (!defs)
return;
pthread_rwlock_wrlock(&snd_rwlock);
list_for_each_safe(snd_card_node, temp, &snd_card_list) {
card_def = node_to_item(snd_card_node, struct snd_dev_def_card, list_node);
if (card_def == defs) {
card_def->refcnt--;
if (!card_def->refcnt) {
list_remove(snd_card_node);
snd_free_card_def(card_def);
}
}
}
pthread_rwlock_unlock(&snd_rwlock);
}
void *snd_card_def_get_node(void *card_node, unsigned int id, int type)
{
struct snd_dev_def_card *card_def = (struct snd_dev_def_card *)card_node;
struct snd_dev_def *dev_def = NULL;
struct listnode *dev_node, *temp, *devs_list;
if (!card_def)
return NULL;
if (type >= SND_NODE_TYPE_MAX)
return NULL;
pthread_rwlock_rdlock(&snd_rwlock);
if (type == SND_NODE_TYPE_PCM)
devs_list = &card_def->pcm_devs_list;
else if (type == SND_NODE_TYPE_COMPR)
devs_list = &card_def->compr_devs_list;
else
devs_list = &card_def->mixer_devs_list;
list_for_each_safe(dev_node, temp, devs_list) {
dev_def = node_to_item(dev_node, struct snd_dev_def, list_node);
if (dev_def->device == id) {
pthread_rwlock_unlock(&snd_rwlock);
return dev_def;
}
}
pthread_rwlock_unlock(&snd_rwlock);
return NULL;
}
int snd_card_def_get_num_node(void *card_node, int type)
{
struct snd_dev_def_card *card_def = (struct snd_dev_def_card *)card_node;
struct listnode *temp, *dev_node, *devs_list;
int num_devs = 0;
if (!card_def)
return 0;
if (type >= SND_NODE_TYPE_MAX)
return 0;
pthread_rwlock_rdlock(&snd_rwlock);
if (type == SND_NODE_TYPE_PCM)
devs_list = &card_def->pcm_devs_list;
else if (type == SND_NODE_TYPE_COMPR)
devs_list = &card_def->compr_devs_list;
else
devs_list = &card_def->mixer_devs_list;
list_for_each_safe(dev_node, temp, devs_list)
num_devs++;
pthread_rwlock_unlock(&snd_rwlock);
return num_devs;
}
int snd_card_def_get_nodes_for_type(void *card_node, int type,
void **list, int num_nodes)
{
struct snd_dev_def_card *card_def = (struct snd_dev_def_card *)card_node;
struct snd_dev_def *dev_def;
struct listnode *temp, *dev_node, *devs_list;
int num_devs = 0, i = 0;
if (!card_def)
return -EINVAL;
if (type >= SND_NODE_TYPE_MAX)
return -EINVAL;
pthread_rwlock_rdlock(&snd_rwlock);
if (type == SND_NODE_TYPE_PCM)
devs_list = &card_def->pcm_devs_list;
else if (type == SND_NODE_TYPE_COMPR)
devs_list = &card_def->compr_devs_list;
else
devs_list = &card_def->mixer_devs_list;
list_for_each_safe(dev_node, temp, devs_list)
num_devs++;
if (num_nodes > num_devs)
return -EINVAL;
list_for_each_safe(dev_node, temp, devs_list) {
dev_def = node_to_item(dev_node, struct snd_dev_def, list_node);
list[i++] = dev_def;
if (i == num_nodes)
break;
}
pthread_rwlock_unlock(&snd_rwlock);
return 0;
}
int snd_card_def_get_int(void *node, const char *prop, int *val)
{
struct snd_dev_def *dev_def = (struct snd_dev_def *)node;
struct snd_prop_val_pair *pv_pair;
struct listnode *pv_pair_node, *temp;
int ret = -EINVAL;
if (!dev_def)
return ret;
pthread_rwlock_rdlock(&snd_rwlock);
if (!strcmp(prop, "type")) {
*val = dev_def->type;
pthread_rwlock_unlock(&snd_rwlock);
return 0;
} else if (!strcmp(prop, "id")) {
*val = dev_def->device;
pthread_rwlock_unlock(&snd_rwlock);
return 0;
}
list_for_each_safe(pv_pair_node, temp, &dev_def->prop_val_list) {
pv_pair = node_to_item(pv_pair_node, struct snd_prop_val_pair, list_node);
if (!strcmp(pv_pair->prop, prop)) {
*val = atoi(pv_pair->val);
pthread_rwlock_unlock(&snd_rwlock);
return 0;
}
}
pthread_rwlock_unlock(&snd_rwlock);
return ret;
}
int snd_card_def_get_str(void *node, const char *prop, char **val)
{
struct snd_dev_def *dev_def = (struct snd_dev_def *)node;
struct snd_prop_val_pair *pv_pair;
struct listnode *pv_pair_node, *temp;
int ret = -EINVAL;
if (!dev_def)
return ret;
pthread_rwlock_rdlock(&snd_rwlock);
if (!strcmp(prop, "so-name")) {
if (dev_def->so_name)
*val = dev_def->so_name;
pthread_rwlock_unlock(&snd_rwlock);
return 0;
}
if (!strcmp(prop, "name")) {
if (dev_def->name)
*val = dev_def->name;
pthread_rwlock_unlock(&snd_rwlock);
return 0;
}
list_for_each_safe(pv_pair_node, temp, &dev_def->prop_val_list) {
pv_pair = node_to_item(pv_pair_node, struct snd_prop_val_pair, list_node);
if (!strcmp(pv_pair->prop, prop)) {
*val = pv_pair->val;
pthread_rwlock_unlock(&snd_rwlock);
return 0;
}
}
pthread_rwlock_unlock(&snd_rwlock);
return ret;
}