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