xref: /linux/tools/testing/selftests/alsa/conf.c (revision 33e02dc69afbd8f1b85a51d74d72f139ba4ca623)
1 // SPDX-License-Identifier: GPL-2.0
2 //
3 // kselftest configuration helpers for the hw specific configuration
4 //
5 // Original author: Jaroslav Kysela <perex@perex.cz>
6 // Copyright (c) 2022 Red Hat Inc.
7 
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <stdbool.h>
11 #include <errno.h>
12 #include <assert.h>
13 #include <dirent.h>
14 #include <regex.h>
15 #include <sys/stat.h>
16 
17 #include "../kselftest.h"
18 #include "alsa-local.h"
19 
20 #define SYSFS_ROOT "/sys"
21 
22 struct card_cfg_data *conf_cards;
23 
24 static const char *alsa_config =
25 "ctl.hw {\n"
26 "	@args [ CARD ]\n"
27 "	@args.CARD.type string\n"
28 "	type hw\n"
29 "	card $CARD\n"
30 "}\n"
31 "pcm.hw {\n"
32 "	@args [ CARD DEV SUBDEV ]\n"
33 "	@args.CARD.type string\n"
34 "	@args.DEV.type integer\n"
35 "	@args.SUBDEV.type integer\n"
36 "	type hw\n"
37 "	card $CARD\n"
38 "	device $DEV\n"
39 "	subdevice $SUBDEV\n"
40 "}\n"
41 ;
42 
43 #ifdef SND_LIB_VER
44 #if SND_LIB_VERSION >= SND_LIB_VER(1, 2, 6)
45 #define LIB_HAS_LOAD_STRING
46 #endif
47 #endif
48 
49 #ifndef LIB_HAS_LOAD_STRING
snd_config_load_string(snd_config_t ** config,const char * s,size_t size)50 static int snd_config_load_string(snd_config_t **config, const char *s,
51 				  size_t size)
52 {
53 	snd_input_t *input;
54 	snd_config_t *dst;
55 	int err;
56 
57 	assert(config && s);
58 	if (size == 0)
59 		size = strlen(s);
60 	err = snd_input_buffer_open(&input, s, size);
61 	if (err < 0)
62 		return err;
63 	err = snd_config_top(&dst);
64 	if (err < 0) {
65 		snd_input_close(input);
66 		return err;
67 	}
68 	err = snd_config_load(dst, input);
69 	snd_input_close(input);
70 	if (err < 0) {
71 		snd_config_delete(dst);
72 		return err;
73 	}
74 	*config = dst;
75 	return 0;
76 }
77 #endif
78 
get_alsalib_config(void)79 snd_config_t *get_alsalib_config(void)
80 {
81 	snd_config_t *config;
82 	int err;
83 
84 	err = snd_config_load_string(&config, alsa_config, strlen(alsa_config));
85 	if (err < 0) {
86 		ksft_print_msg("Unable to parse custom alsa-lib configuration: %s\n",
87 			       snd_strerror(err));
88 		ksft_exit_fail();
89 	}
90 	return config;
91 }
92 
conf_data_by_card(int card,bool msg)93 static struct card_cfg_data *conf_data_by_card(int card, bool msg)
94 {
95 	struct card_cfg_data *conf;
96 
97 	for (conf = conf_cards; conf; conf = conf->next) {
98 		if (conf->card == card) {
99 			if (msg)
100 				ksft_print_msg("using hw card config %s for card %d\n",
101 					       conf->filename, card);
102 			return conf;
103 		}
104 	}
105 	return NULL;
106 }
107 
dump_config_tree(snd_config_t * top)108 static void dump_config_tree(snd_config_t *top)
109 {
110 	snd_output_t *out;
111 	int err;
112 
113 	err = snd_output_stdio_attach(&out, stdout, 0);
114 	if (err < 0)
115 		ksft_exit_fail_msg("stdout attach\n");
116 	if (snd_config_save(top, out))
117 		ksft_exit_fail_msg("config save\n");
118 	snd_output_close(out);
119 }
120 
conf_load_from_file(const char * filename)121 snd_config_t *conf_load_from_file(const char *filename)
122 {
123 	snd_config_t *dst;
124 	snd_input_t *input;
125 	int err;
126 
127 	err = snd_input_stdio_open(&input, filename, "r");
128 	if (err < 0)
129 		ksft_exit_fail_msg("Unable to parse filename %s\n", filename);
130 	err = snd_config_top(&dst);
131 	if (err < 0)
132 		ksft_exit_fail_msg("Out of memory\n");
133 	err = snd_config_load(dst, input);
134 	snd_input_close(input);
135 	if (err < 0)
136 		ksft_exit_fail_msg("Unable to parse filename %s\n", filename);
137 	return dst;
138 }
139 
sysfs_get(const char * sysfs_root,const char * id)140 static char *sysfs_get(const char *sysfs_root, const char *id)
141 {
142 	char path[PATH_MAX], link[PATH_MAX + 1];
143 	struct stat sb;
144 	ssize_t len;
145 	char *e;
146 	int fd;
147 
148 	if (id[0] == '/')
149 		id++;
150 	snprintf(path, sizeof(path), "%s/%s", sysfs_root, id);
151 	if (lstat(path, &sb) != 0)
152 		return NULL;
153 	if (S_ISLNK(sb.st_mode)) {
154 		len = readlink(path, link, sizeof(link) - 1);
155 		if (len <= 0) {
156 			ksft_exit_fail_msg("sysfs: cannot read link '%s': %s\n",
157 					   path, strerror(errno));
158 			return NULL;
159 		}
160 		link[len] = '\0';
161 		e = strrchr(link, '/');
162 		if (e)
163 			return strdup(e + 1);
164 		return NULL;
165 	}
166 	if (S_ISDIR(sb.st_mode))
167 		return NULL;
168 	if ((sb.st_mode & S_IRUSR) == 0)
169 		return NULL;
170 
171 	fd = open(path, O_RDONLY);
172 	if (fd < 0) {
173 		if (errno == ENOENT)
174 			return NULL;
175 		ksft_exit_fail_msg("sysfs: open failed for '%s': %s\n",
176 				   path, strerror(errno));
177 	}
178 	len = read(fd, path, sizeof(path)-1);
179 	close(fd);
180 	if (len < 0)
181 		ksft_exit_fail_msg("sysfs: unable to read value '%s': %s\n",
182 				   path, strerror(errno));
183 	while (len > 0 && path[len-1] == '\n')
184 		len--;
185 	path[len] = '\0';
186 	e = strdup(path);
187 	if (e == NULL)
188 		ksft_exit_fail_msg("Out of memory\n");
189 	return e;
190 }
191 
sysfs_match(const char * sysfs_root,snd_config_t * config)192 static bool sysfs_match(const char *sysfs_root, snd_config_t *config)
193 {
194 	snd_config_t *node, *path_config, *regex_config;
195 	snd_config_iterator_t i, next;
196 	const char *path_string, *regex_string, *v;
197 	regex_t re;
198 	regmatch_t match[1];
199 	int iter = 0, ret;
200 
201 	snd_config_for_each(i, next, config) {
202 		node = snd_config_iterator_entry(i);
203 		if (snd_config_search(node, "path", &path_config))
204 			ksft_exit_fail_msg("Missing path field in the sysfs block\n");
205 		if (snd_config_search(node, "regex", &regex_config))
206 			ksft_exit_fail_msg("Missing regex field in the sysfs block\n");
207 		if (snd_config_get_string(path_config, &path_string))
208 			ksft_exit_fail_msg("Path field in the sysfs block is not a string\n");
209 		if (snd_config_get_string(regex_config, &regex_string))
210 			ksft_exit_fail_msg("Regex field in the sysfs block is not a string\n");
211 		iter++;
212 		v = sysfs_get(sysfs_root, path_string);
213 		if (!v)
214 			return false;
215 		if (regcomp(&re, regex_string, REG_EXTENDED))
216 			ksft_exit_fail_msg("Wrong regex '%s'\n", regex_string);
217 		ret = regexec(&re, v, 1, match, 0);
218 		regfree(&re);
219 		if (ret)
220 			return false;
221 	}
222 	return iter > 0;
223 }
224 
assign_card_config(int card,const char * sysfs_card_root)225 static void assign_card_config(int card, const char *sysfs_card_root)
226 {
227 	struct card_cfg_data *data;
228 	snd_config_t *sysfs_card_config;
229 
230 	for (data = conf_cards; data; data = data->next) {
231 		snd_config_search(data->config, "sysfs", &sysfs_card_config);
232 		if (!sysfs_match(sysfs_card_root, sysfs_card_config))
233 			continue;
234 
235 		data->card = card;
236 		break;
237 	}
238 }
239 
assign_card_configs(void)240 static void assign_card_configs(void)
241 {
242 	char fn[128];
243 	int card;
244 
245 	for (card = 0; card < 32; card++) {
246 		snprintf(fn, sizeof(fn), "%s/class/sound/card%d", SYSFS_ROOT, card);
247 		if (access(fn, R_OK) == 0)
248 			assign_card_config(card, fn);
249 	}
250 }
251 
filename_filter(const struct dirent * dirent)252 static int filename_filter(const struct dirent *dirent)
253 {
254 	size_t flen;
255 
256 	if (dirent == NULL)
257 		return 0;
258 	if (dirent->d_type == DT_DIR)
259 		return 0;
260 	flen = strlen(dirent->d_name);
261 	if (flen <= 5)
262 		return 0;
263 	if (strncmp(&dirent->d_name[flen-5], ".conf", 5) == 0)
264 		return 1;
265 	return 0;
266 }
267 
match_config(const char * filename)268 static bool match_config(const char *filename)
269 {
270 	struct card_cfg_data *data;
271 	snd_config_t *config, *sysfs_config, *card_config, *sysfs_card_config, *node;
272 	snd_config_iterator_t i, next;
273 
274 	config = conf_load_from_file(filename);
275 	if (snd_config_search(config, "sysfs", &sysfs_config) ||
276 	    snd_config_get_type(sysfs_config) != SND_CONFIG_TYPE_COMPOUND)
277 		ksft_exit_fail_msg("Missing global sysfs block in filename %s\n", filename);
278 	if (snd_config_search(config, "card", &card_config) ||
279 	    snd_config_get_type(card_config) != SND_CONFIG_TYPE_COMPOUND)
280 		ksft_exit_fail_msg("Missing global card block in filename %s\n", filename);
281 	if (!sysfs_match(SYSFS_ROOT, sysfs_config))
282 		return false;
283 	snd_config_for_each(i, next, card_config) {
284 		node = snd_config_iterator_entry(i);
285 		if (snd_config_search(node, "sysfs", &sysfs_card_config) ||
286 		    snd_config_get_type(sysfs_card_config) != SND_CONFIG_TYPE_COMPOUND)
287 			ksft_exit_fail_msg("Missing card sysfs block in filename %s\n", filename);
288 
289 		data = malloc(sizeof(*data));
290 		if (!data)
291 			ksft_exit_fail_msg("Out of memory\n");
292 		data->filename = filename;
293 		data->config = node;
294 		data->card = -1;
295 		if (snd_config_get_id(node, &data->config_id))
296 			ksft_exit_fail_msg("snd_config_get_id failed for card\n");
297 		data->next = conf_cards;
298 		conf_cards = data;
299 	}
300 	return true;
301 }
302 
conf_load(void)303 void conf_load(void)
304 {
305 	const char *fn = "conf.d";
306 	struct dirent **namelist;
307 	int n, j;
308 
309 	n = scandir(fn, &namelist, filename_filter, alphasort);
310 	if (n < 0)
311 		ksft_exit_fail_msg("scandir: %s\n", strerror(errno));
312 	for (j = 0; j < n; j++) {
313 		size_t sl = strlen(fn) + strlen(namelist[j]->d_name) + 2;
314 		char *filename = malloc(sl);
315 		if (filename == NULL)
316 			ksft_exit_fail_msg("Out of memory\n");
317 		sprintf(filename, "%s/%s", fn, namelist[j]->d_name);
318 		if (match_config(filename))
319 			filename = NULL;
320 		free(filename);
321 		free(namelist[j]);
322 	}
323 	free(namelist);
324 
325 	assign_card_configs();
326 }
327 
conf_free(void)328 void conf_free(void)
329 {
330 	struct card_cfg_data *conf;
331 
332 	while (conf_cards) {
333 		conf = conf_cards;
334 		conf_cards = conf->next;
335 		snd_config_delete(conf->config);
336 	}
337 }
338 
conf_by_card(int card)339 snd_config_t *conf_by_card(int card)
340 {
341 	struct card_cfg_data *conf;
342 
343 	conf = conf_data_by_card(card, true);
344 	if (conf)
345 		return conf->config;
346 	return NULL;
347 }
348 
conf_get_by_keys(snd_config_t * root,const char * key1,const char * key2,snd_config_t ** result)349 static int conf_get_by_keys(snd_config_t *root, const char *key1,
350 			    const char *key2, snd_config_t **result)
351 {
352 	int ret;
353 
354 	if (key1) {
355 		ret = snd_config_search(root, key1, &root);
356 		if (ret != -ENOENT && ret < 0)
357 			return ret;
358 	}
359 	if (key2)
360 		ret = snd_config_search(root, key2, &root);
361 	if (ret >= 0)
362 		*result = root;
363 	return ret;
364 }
365 
conf_get_subtree(snd_config_t * root,const char * key1,const char * key2)366 snd_config_t *conf_get_subtree(snd_config_t *root, const char *key1, const char *key2)
367 {
368 	int ret;
369 
370 	if (!root)
371 		return NULL;
372 	ret = conf_get_by_keys(root, key1, key2, &root);
373 	if (ret == -ENOENT)
374 		return NULL;
375 	if (ret < 0)
376 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
377 	return root;
378 }
379 
conf_get_count(snd_config_t * root,const char * key1,const char * key2)380 int conf_get_count(snd_config_t *root, const char *key1, const char *key2)
381 {
382 	snd_config_t *cfg;
383 	snd_config_iterator_t i, next;
384 	int count, ret;
385 
386 	if (!root)
387 		return -1;
388 	ret = conf_get_by_keys(root, key1, key2, &cfg);
389 	if (ret == -ENOENT)
390 		return -1;
391 	if (ret < 0)
392 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
393 	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND)
394 		ksft_exit_fail_msg("key '%s'.'%s' is not a compound\n", key1, key2);
395 	count = 0;
396 	snd_config_for_each(i, next, cfg)
397 		count++;
398 	return count;
399 }
400 
conf_get_string(snd_config_t * root,const char * key1,const char * key2,const char * def)401 const char *conf_get_string(snd_config_t *root, const char *key1, const char *key2, const char *def)
402 {
403 	snd_config_t *cfg;
404 	const char *s;
405 	int ret;
406 
407 	if (!root)
408 		return def;
409 	ret = conf_get_by_keys(root, key1, key2, &cfg);
410 	if (ret == -ENOENT)
411 		return def;
412 	if (ret < 0)
413 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
414 	if (snd_config_get_string(cfg, &s))
415 		ksft_exit_fail_msg("key '%s'.'%s' is not a string\n", key1, key2);
416 	return s;
417 }
418 
conf_get_long(snd_config_t * root,const char * key1,const char * key2,long def)419 long conf_get_long(snd_config_t *root, const char *key1, const char *key2, long def)
420 {
421 	snd_config_t *cfg;
422 	long l;
423 	int ret;
424 
425 	if (!root)
426 		return def;
427 	ret = conf_get_by_keys(root, key1, key2, &cfg);
428 	if (ret == -ENOENT)
429 		return def;
430 	if (ret < 0)
431 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
432 	if (snd_config_get_integer(cfg, &l))
433 		ksft_exit_fail_msg("key '%s'.'%s' is not an integer\n", key1, key2);
434 	return l;
435 }
436 
conf_get_bool(snd_config_t * root,const char * key1,const char * key2,int def)437 int conf_get_bool(snd_config_t *root, const char *key1, const char *key2, int def)
438 {
439 	snd_config_t *cfg;
440 	int ret;
441 
442 	if (!root)
443 		return def;
444 	ret = conf_get_by_keys(root, key1, key2, &cfg);
445 	if (ret == -ENOENT)
446 		return def;
447 	if (ret < 0)
448 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
449 	ret = snd_config_get_bool(cfg);
450 	if (ret < 0)
451 		ksft_exit_fail_msg("key '%s'.'%s' is not an bool\n", key1, key2);
452 	return !!ret;
453 }
454 
conf_get_string_array(snd_config_t * root,const char * key1,const char * key2,const char ** array,int array_size,const char * def)455 void conf_get_string_array(snd_config_t *root, const char *key1, const char *key2,
456 			   const char **array, int array_size, const char *def)
457 {
458 	snd_config_t *cfg;
459 	char buf[16];
460 	int ret, index;
461 
462 	ret = conf_get_by_keys(root, key1, key2, &cfg);
463 	if (ret == -ENOENT)
464 		cfg = NULL;
465 	else if (ret < 0)
466 		ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
467 	for (index = 0; index < array_size; index++) {
468 		if (cfg == NULL) {
469 			array[index] = def;
470 		} else {
471 			sprintf(buf, "%i", index);
472 			array[index] = conf_get_string(cfg, buf, NULL, def);
473 		}
474 	}
475 }
476