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("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("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 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 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 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 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, 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("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 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 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("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("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 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 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 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 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