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 |
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 | |
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(msg: "Unable to parse custom alsa-lib configuration: %s\n" , |
87 | snd_strerror(err)); |
88 | ksft_exit_fail(); |
89 | } |
90 | return config; |
91 | } |
92 | |
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(msg: "using hw card config %s for card %d\n" , |
101 | conf->filename, card); |
102 | return conf; |
103 | } |
104 | } |
105 | return NULL; |
106 | } |
107 | |
108 | static int 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(msg: "stdout attach\n" ); |
116 | if (snd_config_save(top, out)) |
117 | ksft_exit_fail_msg(msg: "config save\n" ); |
118 | snd_output_close(out); |
119 | } |
120 | |
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(msg: "Unable to parse filename %s\n" , filename); |
130 | err = snd_config_top(&dst); |
131 | if (err < 0) |
132 | ksft_exit_fail_msg(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(msg: "Unable to parse filename %s\n" , filename); |
137 | return dst; |
138 | } |
139 | |
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(msg: "Out of memory\n" ); |
189 | return e; |
190 | } |
191 | |
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" , ®ex_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, ®ex_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 | |
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 | |
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, sysfs_card_root: fn); |
249 | } |
250 | } |
251 | |
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 | |
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(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(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 | |
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(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 | |
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 | |
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 | |
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 | |
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(msg: "key '%s'.'%s' search error: %s\n" , key1, key2, snd_strerror(ret)); |
377 | return root; |
378 | } |
379 | |
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(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(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 | |
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(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(msg: "key '%s'.'%s' is not a string\n" , key1, key2); |
416 | return s; |
417 | } |
418 | |
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(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(msg: "key '%s'.'%s' is not an integer\n" , key1, key2); |
434 | return l; |
435 | } |
436 | |
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(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(msg: "key '%s'.'%s' is not an bool\n" , key1, key2); |
452 | return !!ret; |
453 | } |
454 | |
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(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 | |