475 lines · 11.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0//// kselftest configuration helpers for the hw specific configuration//// Original author: Jaroslav Kysela <perex@perex.cz>// Copyright (c) 2022 Red Hat Inc. #include <stdio.h>#include <stdlib.h>#include <stdbool.h>#include <errno.h>#include <assert.h>#include <dirent.h>#include <regex.h>#include <sys/stat.h> #include "kselftest.h"#include "alsa-local.h" #define SYSFS_ROOT "/sys" struct card_cfg_data *conf_cards; static const char *alsa_config ="ctl.hw {\n""	@args [ CARD ]\n""	@args.CARD.type string\n""	type hw\n""	card $CARD\n""}\n""pcm.hw {\n""	@args [ CARD DEV SUBDEV ]\n""	@args.CARD.type string\n""	@args.DEV.type integer\n""	@args.SUBDEV.type integer\n""	type hw\n""	card $CARD\n""	device $DEV\n""	subdevice $SUBDEV\n""}\n"; #ifdef SND_LIB_VER#if SND_LIB_VERSION >= SND_LIB_VER(1, 2, 6)#define LIB_HAS_LOAD_STRING#endif#endif #ifndef LIB_HAS_LOAD_STRINGstatic int snd_config_load_string(snd_config_t **config, const char *s,				  size_t size){	snd_input_t *input;	snd_config_t *dst;	int err; 	assert(config && s);	if (size == 0)		size = strlen(s);	err = snd_input_buffer_open(&input, s, size);	if (err < 0)		return err;	err = snd_config_top(&dst);	if (err < 0) {		snd_input_close(input);		return err;	}	err = snd_config_load(dst, input);	snd_input_close(input);	if (err < 0) {		snd_config_delete(dst);		return err;	}	*config = dst;	return 0;}#endif snd_config_t *get_alsalib_config(void){	snd_config_t *config;	int err; 	err = snd_config_load_string(&config, alsa_config, strlen(alsa_config));	if (err < 0) {		ksft_print_msg("Unable to parse custom alsa-lib configuration: %s\n",			       snd_strerror(err));		ksft_exit_fail();	}	return config;} static struct card_cfg_data *conf_data_by_card(int card, bool msg){	struct card_cfg_data *conf; 	for (conf = conf_cards; conf; conf = conf->next) {		if (conf->card == card) {			if (msg)				ksft_print_msg("using hw card config %s for card %d\n",					       conf->filename, card);			return conf;		}	}	return NULL;} static void dump_config_tree(snd_config_t *top){	snd_output_t *out;	int err; 	err = snd_output_stdio_attach(&out, stdout, 0);	if (err < 0)		ksft_exit_fail_msg("stdout attach\n");	if (snd_config_save(top, out))		ksft_exit_fail_msg("config save\n");	snd_output_close(out);} snd_config_t *conf_load_from_file(const char *filename){	snd_config_t *dst;	snd_input_t *input;	int err; 	err = snd_input_stdio_open(&input, filename, "r");	if (err < 0)		ksft_exit_fail_msg("Unable to parse filename %s\n", filename);	err = snd_config_top(&dst);	if (err < 0)		ksft_exit_fail_msg("Out of memory\n");	err = snd_config_load(dst, input);	snd_input_close(input);	if (err < 0)		ksft_exit_fail_msg("Unable to parse filename %s\n", filename);	return dst;} static char *sysfs_get(const char *sysfs_root, const char *id){	char path[PATH_MAX], link[PATH_MAX + 1];	struct stat sb;	ssize_t len;	char *e;	int fd; 	if (id[0] == '/')		id++;	snprintf(path, sizeof(path), "%s/%s", sysfs_root, id);	if (lstat(path, &sb) != 0)		return NULL;	if (S_ISLNK(sb.st_mode)) {		len = readlink(path, link, sizeof(link) - 1);		if (len <= 0) {			ksft_exit_fail_msg("sysfs: cannot read link '%s': %s\n",					   path, strerror(errno));			return NULL;		}		link[len] = '\0';		e = strrchr(link, '/');		if (e)			return strdup(e + 1);		return NULL;	}	if (S_ISDIR(sb.st_mode))		return NULL;	if ((sb.st_mode & S_IRUSR) == 0)		return NULL; 	fd = open(path, O_RDONLY);	if (fd < 0) {		if (errno == ENOENT)			return NULL;		ksft_exit_fail_msg("sysfs: open failed for '%s': %s\n",				   path, strerror(errno));	}	len = read(fd, path, sizeof(path)-1);	close(fd);	if (len < 0)		ksft_exit_fail_msg("sysfs: unable to read value '%s': %s\n",				   path, strerror(errno));	while (len > 0 && path[len-1] == '\n')		len--;	path[len] = '\0';	e = strdup(path);	if (e == NULL)		ksft_exit_fail_msg("Out of memory\n");	return e;} static bool sysfs_match(const char *sysfs_root, snd_config_t *config){	snd_config_t *node, *path_config, *regex_config;	snd_config_iterator_t i, next;	const char *path_string, *regex_string, *v;	regex_t re;	regmatch_t match[1];	int iter = 0, ret; 	snd_config_for_each(i, next, config) {		node = snd_config_iterator_entry(i);		if (snd_config_search(node, "path", &path_config))			ksft_exit_fail_msg("Missing path field in the sysfs block\n");		if (snd_config_search(node, "regex", &regex_config))			ksft_exit_fail_msg("Missing regex field in the sysfs block\n");		if (snd_config_get_string(path_config, &path_string))			ksft_exit_fail_msg("Path field in the sysfs block is not a string\n");		if (snd_config_get_string(regex_config, &regex_string))			ksft_exit_fail_msg("Regex field in the sysfs block is not a string\n");		iter++;		v = sysfs_get(sysfs_root, path_string);		if (!v)			return false;		if (regcomp(&re, regex_string, REG_EXTENDED))			ksft_exit_fail_msg("Wrong regex '%s'\n", regex_string);		ret = regexec(&re, v, 1, match, 0);		regfree(&re);		if (ret)			return false;	}	return iter > 0;} static void assign_card_config(int card, const char *sysfs_card_root){	struct card_cfg_data *data;	snd_config_t *sysfs_card_config; 	for (data = conf_cards; data; data = data->next) {		snd_config_search(data->config, "sysfs", &sysfs_card_config);		if (!sysfs_match(sysfs_card_root, sysfs_card_config))			continue; 		data->card = card;		break;	}} static void assign_card_configs(void){	char fn[128];	int card; 	for (card = 0; card < 32; card++) {		snprintf(fn, sizeof(fn), "%s/class/sound/card%d", SYSFS_ROOT, card);		if (access(fn, R_OK) == 0)			assign_card_config(card, fn);	}} static int filename_filter(const struct dirent *dirent){	size_t flen; 	if (dirent == NULL)		return 0;	if (dirent->d_type == DT_DIR)		return 0;	flen = strlen(dirent->d_name);	if (flen <= 5)		return 0;	if (strncmp(&dirent->d_name[flen-5], ".conf", 5) == 0)		return 1;	return 0;} static bool match_config(const char *filename){	struct card_cfg_data *data;	snd_config_t *config, *sysfs_config, *card_config, *sysfs_card_config, *node;	snd_config_iterator_t i, next; 	config = conf_load_from_file(filename);	if (snd_config_search(config, "sysfs", &sysfs_config) ||	    snd_config_get_type(sysfs_config) != SND_CONFIG_TYPE_COMPOUND)		ksft_exit_fail_msg("Missing global sysfs block in filename %s\n", filename);	if (snd_config_search(config, "card", &card_config) ||	    snd_config_get_type(card_config) != SND_CONFIG_TYPE_COMPOUND)		ksft_exit_fail_msg("Missing global card block in filename %s\n", filename);	if (!sysfs_match(SYSFS_ROOT, sysfs_config))		return false;	snd_config_for_each(i, next, card_config) {		node = snd_config_iterator_entry(i);		if (snd_config_search(node, "sysfs", &sysfs_card_config) ||		    snd_config_get_type(sysfs_card_config) != SND_CONFIG_TYPE_COMPOUND)			ksft_exit_fail_msg("Missing card sysfs block in filename %s\n", filename); 		data = malloc(sizeof(*data));		if (!data)			ksft_exit_fail_msg("Out of memory\n");		data->filename = filename;		data->config = node;		data->card = -1;		if (snd_config_get_id(node, &data->config_id))			ksft_exit_fail_msg("snd_config_get_id failed for card\n");		data->next = conf_cards;		conf_cards = data;	}	return true;} void conf_load(void){	const char *fn = "conf.d";	struct dirent **namelist;	int n, j; 	n = scandir(fn, &namelist, filename_filter, alphasort);	if (n < 0)		ksft_exit_fail_msg("scandir: %s\n", strerror(errno));	for (j = 0; j < n; j++) {		size_t sl = strlen(fn) + strlen(namelist[j]->d_name) + 2;		char *filename = malloc(sl);		if (filename == NULL)			ksft_exit_fail_msg("Out of memory\n");		sprintf(filename, "%s/%s", fn, namelist[j]->d_name);		if (match_config(filename))			filename = NULL;		free(filename);		free(namelist[j]);	}	free(namelist); 	assign_card_configs();} void conf_free(void){	struct card_cfg_data *conf; 	while (conf_cards) {		conf = conf_cards;		conf_cards = conf->next;		snd_config_delete(conf->config);	}} snd_config_t *conf_by_card(int card){	struct card_cfg_data *conf; 	conf = conf_data_by_card(card, true);	if (conf)		return conf->config;	return NULL;} static int conf_get_by_keys(snd_config_t *root, const char *key1,			    const char *key2, snd_config_t **result){	int ret; 	if (key1) {		ret = snd_config_search(root, key1, &root);		if (ret != -ENOENT && ret < 0)			return ret;	}	if (key2)		ret = snd_config_search(root, key2, &root);	if (ret >= 0)		*result = root;	return ret;} snd_config_t *conf_get_subtree(snd_config_t *root, const char *key1, const char *key2){	int ret; 	if (!root)		return NULL;	ret = conf_get_by_keys(root, key1, key2, &root);	if (ret == -ENOENT)		return NULL;	if (ret < 0)		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));	return root;} int conf_get_count(snd_config_t *root, const char *key1, const char *key2){	snd_config_t *cfg;	snd_config_iterator_t i, next;	int count, ret; 	if (!root)		return -1;	ret = conf_get_by_keys(root, key1, key2, &cfg);	if (ret == -ENOENT)		return -1;	if (ret < 0)		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND)		ksft_exit_fail_msg("key '%s'.'%s' is not a compound\n", key1, key2);	count = 0;	snd_config_for_each(i, next, cfg)		count++;	return count;} const char *conf_get_string(snd_config_t *root, const char *key1, const char *key2, const char *def){	snd_config_t *cfg;	const char *s;	int ret; 	if (!root)		return def;	ret = conf_get_by_keys(root, key1, key2, &cfg);	if (ret == -ENOENT)		return def;	if (ret < 0)		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));	if (snd_config_get_string(cfg, &s))		ksft_exit_fail_msg("key '%s'.'%s' is not a string\n", key1, key2);	return s;} long conf_get_long(snd_config_t *root, const char *key1, const char *key2, long def){	snd_config_t *cfg;	long l;	int ret; 	if (!root)		return def;	ret = conf_get_by_keys(root, key1, key2, &cfg);	if (ret == -ENOENT)		return def;	if (ret < 0)		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));	if (snd_config_get_integer(cfg, &l))		ksft_exit_fail_msg("key '%s'.'%s' is not an integer\n", key1, key2);	return l;} int conf_get_bool(snd_config_t *root, const char *key1, const char *key2, int def){	snd_config_t *cfg;	int ret; 	if (!root)		return def;	ret = conf_get_by_keys(root, key1, key2, &cfg);	if (ret == -ENOENT)		return def;	if (ret < 0)		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));	ret = snd_config_get_bool(cfg);	if (ret < 0)		ksft_exit_fail_msg("key '%s'.'%s' is not a bool\n", key1, key2);	return !!ret;} void conf_get_string_array(snd_config_t *root, const char *key1, const char *key2,			   const char **array, int array_size, const char *def){	snd_config_t *cfg;	char buf[16];	int ret, index; 	ret = conf_get_by_keys(root, key1, key2, &cfg);	if (ret == -ENOENT)		cfg = NULL;	else if (ret < 0)		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));	for (index = 0; index < array_size; index++) {		if (cfg == NULL) {			array[index] = def;		} else {			sprintf(buf, "%i", index);			array[index] = conf_get_string(cfg, buf, NULL, def);		}	}}