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