1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // kselftest for the ALSA PCM API 4 // 5 // Original author: Jaroslav Kysela <perex@perex.cz> 6 // Copyright (c) 2022 Red Hat Inc. 7 8 // This test will iterate over all cards detected in the system, exercising 9 // every PCM device it can find. This may conflict with other system 10 // software if there is audio activity so is best run on a system with a 11 // minimal active userspace. 12 13 #include <stdio.h> 14 #include <stdlib.h> 15 #include <stdbool.h> 16 #include <errno.h> 17 #include <assert.h> 18 #include <pthread.h> 19 20 #include "../kselftest.h" 21 #include "alsa-local.h" 22 23 typedef struct timespec timestamp_t; 24 25 struct card_data { 26 int card; 27 snd_ctl_card_info_t *info; 28 const char *name; 29 pthread_t thread; 30 struct card_data *next; 31 }; 32 33 struct card_data *card_list = NULL; 34 35 struct pcm_data { 36 snd_pcm_t *handle; 37 int card; 38 int device; 39 int subdevice; 40 const char *card_name; 41 snd_pcm_stream_t stream; 42 snd_config_t *pcm_config; 43 struct pcm_data *next; 44 }; 45 46 struct pcm_data *pcm_list = NULL; 47 48 int num_missing = 0; 49 struct pcm_data *pcm_missing = NULL; 50 51 snd_config_t *default_pcm_config; 52 53 /* Lock while reporting results since kselftest doesn't */ 54 pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER; 55 56 enum test_class { 57 TEST_CLASS_DEFAULT, 58 TEST_CLASS_SYSTEM, 59 }; 60 61 void timestamp_now(timestamp_t *tstamp) 62 { 63 if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp)) 64 ksft_exit_fail_msg("clock_get_time\n"); 65 } 66 67 long long timestamp_diff_ms(timestamp_t *tstamp) 68 { 69 timestamp_t now, diff; 70 timestamp_now(&now); 71 if (tstamp->tv_nsec > now.tv_nsec) { 72 diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1; 73 diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec; 74 } else { 75 diff.tv_sec = now.tv_sec - tstamp->tv_sec; 76 diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec; 77 } 78 return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L); 79 } 80 81 static long device_from_id(snd_config_t *node) 82 { 83 const char *id; 84 char *end; 85 long v; 86 87 if (snd_config_get_id(node, &id)) 88 ksft_exit_fail_msg("snd_config_get_id\n"); 89 errno = 0; 90 v = strtol(id, &end, 10); 91 if (errno || *end) 92 return -1; 93 return v; 94 } 95 96 static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream) 97 { 98 struct pcm_data *pcm_data; 99 100 for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) { 101 if (pcm_data->card != card) 102 continue; 103 if (pcm_data->device != device) 104 continue; 105 if (pcm_data->subdevice != subdevice) 106 continue; 107 if (pcm_data->stream != stream) 108 continue; 109 return; 110 } 111 pcm_data = calloc(1, sizeof(*pcm_data)); 112 if (!pcm_data) 113 ksft_exit_fail_msg("Out of memory\n"); 114 pcm_data->card = card; 115 pcm_data->device = device; 116 pcm_data->subdevice = subdevice; 117 pcm_data->stream = stream; 118 pcm_data->next = pcm_missing; 119 pcm_missing = pcm_data; 120 num_missing++; 121 } 122 123 static void missing_devices(int card, snd_config_t *card_config) 124 { 125 snd_config_t *pcm_config, *node1, *node2; 126 snd_config_iterator_t i1, i2, next1, next2; 127 int device, subdevice; 128 129 pcm_config = conf_get_subtree(card_config, "pcm", NULL); 130 if (!pcm_config) 131 return; 132 snd_config_for_each(i1, next1, pcm_config) { 133 node1 = snd_config_iterator_entry(i1); 134 device = device_from_id(node1); 135 if (device < 0) 136 continue; 137 if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND) 138 continue; 139 snd_config_for_each(i2, next2, node1) { 140 node2 = snd_config_iterator_entry(i2); 141 subdevice = device_from_id(node2); 142 if (subdevice < 0) 143 continue; 144 if (conf_get_subtree(node2, "PLAYBACK", NULL)) 145 missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK); 146 if (conf_get_subtree(node2, "CAPTURE", NULL)) 147 missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE); 148 } 149 } 150 } 151 152 static void find_pcms(void) 153 { 154 char name[32], key[64]; 155 char *card_name, *card_longname; 156 int card, dev, subdev, count, direction, err; 157 snd_pcm_stream_t stream; 158 struct pcm_data *pcm_data; 159 snd_ctl_t *handle; 160 snd_pcm_info_t *pcm_info; 161 snd_config_t *config, *card_config, *pcm_config; 162 struct card_data *card_data; 163 164 snd_pcm_info_alloca(&pcm_info); 165 166 card = -1; 167 if (snd_card_next(&card) < 0 || card < 0) 168 return; 169 170 config = get_alsalib_config(); 171 172 while (card >= 0) { 173 card_data = calloc(1, sizeof(*card_data)); 174 if (!card_data) 175 ksft_exit_fail_msg("Out of memory\n"); 176 177 sprintf(name, "hw:%d", card); 178 179 err = snd_ctl_open_lconf(&handle, name, 0, config); 180 if (err < 0) { 181 ksft_print_msg("Failed to get hctl for card %d: %s\n", 182 card, snd_strerror(err)); 183 goto next_card; 184 } 185 186 err = snd_card_get_name(card, &card_name); 187 if (err != 0) 188 card_name = "Unknown"; 189 err = snd_card_get_longname(card, &card_longname); 190 if (err != 0) 191 card_longname = "Unknown"; 192 193 err = snd_ctl_card_info_malloc(&card_data->info); 194 if (err != 0) 195 ksft_exit_fail_msg("Failed to allocate card info: %d\n", 196 err); 197 198 err = snd_ctl_card_info(handle, card_data->info); 199 if (err == 0) { 200 card_data->name = snd_ctl_card_info_get_id(card_data->info); 201 if (!card_data->name) 202 ksft_print_msg("Failed to get card ID\n"); 203 } else { 204 ksft_print_msg("Failed to get card info: %d\n", err); 205 } 206 207 if (!card_data->name) 208 card_data->name = "Unknown"; 209 210 ksft_print_msg("Card %d/%s - %s (%s)\n", card, 211 card_data->name, card_name, card_longname); 212 213 card_config = conf_by_card(card); 214 215 card_data->card = card; 216 card_data->next = card_list; 217 card_list = card_data; 218 219 dev = -1; 220 while (1) { 221 if (snd_ctl_pcm_next_device(handle, &dev) < 0) 222 ksft_exit_fail_msg("snd_ctl_pcm_next_device\n"); 223 if (dev < 0) 224 break; 225 226 for (direction = 0; direction < 2; direction++) { 227 stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK; 228 sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream)); 229 pcm_config = conf_get_subtree(card_config, key, NULL); 230 if (conf_get_bool(card_config, key, "skip", false)) { 231 ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream)); 232 continue; 233 } 234 snd_pcm_info_set_device(pcm_info, dev); 235 snd_pcm_info_set_subdevice(pcm_info, 0); 236 snd_pcm_info_set_stream(pcm_info, stream); 237 err = snd_ctl_pcm_info(handle, pcm_info); 238 if (err == -ENOENT) 239 continue; 240 if (err < 0) 241 ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n", 242 dev, 0, stream); 243 244 ksft_print_msg("%s.0 - %s\n", card_data->name, 245 snd_pcm_info_get_id(pcm_info)); 246 247 count = snd_pcm_info_get_subdevices_count(pcm_info); 248 for (subdev = 0; subdev < count; subdev++) { 249 sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream)); 250 if (conf_get_bool(card_config, key, "skip", false)) { 251 ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev, 252 subdev, snd_pcm_stream_name(stream)); 253 continue; 254 } 255 pcm_data = calloc(1, sizeof(*pcm_data)); 256 if (!pcm_data) 257 ksft_exit_fail_msg("Out of memory\n"); 258 pcm_data->card = card; 259 pcm_data->device = dev; 260 pcm_data->subdevice = subdev; 261 pcm_data->card_name = card_data->name; 262 pcm_data->stream = stream; 263 pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL); 264 pcm_data->next = pcm_list; 265 pcm_list = pcm_data; 266 } 267 } 268 } 269 270 /* check for missing devices */ 271 missing_devices(card, card_config); 272 273 next_card: 274 snd_ctl_close(handle); 275 if (snd_card_next(&card) < 0) { 276 ksft_print_msg("snd_card_next"); 277 break; 278 } 279 } 280 281 snd_config_delete(config); 282 } 283 284 static void test_pcm_time(struct pcm_data *data, enum test_class class, 285 const char *test_name, snd_config_t *pcm_cfg) 286 { 287 char name[64], msg[256]; 288 const int duration_s = 2, margin_ms = 100; 289 const int duration_ms = duration_s * 1000; 290 const char *cs; 291 int i, err; 292 snd_pcm_t *handle = NULL; 293 snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED; 294 snd_pcm_format_t format, old_format; 295 const char *alt_formats[8]; 296 unsigned char *samples = NULL; 297 snd_pcm_sframes_t frames; 298 long long ms; 299 long rate, channels, period_size, buffer_size; 300 unsigned int rrate; 301 snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold; 302 timestamp_t tstamp; 303 bool pass = false; 304 snd_pcm_hw_params_t *hw_params; 305 snd_pcm_sw_params_t *sw_params; 306 const char *test_class_name; 307 bool skip = true; 308 const char *desc; 309 310 switch (class) { 311 case TEST_CLASS_DEFAULT: 312 test_class_name = "default"; 313 break; 314 case TEST_CLASS_SYSTEM: 315 test_class_name = "system"; 316 break; 317 default: 318 ksft_exit_fail_msg("Unknown test class %d\n", class); 319 break; 320 } 321 322 desc = conf_get_string(pcm_cfg, "description", NULL, NULL); 323 if (desc) 324 ksft_print_msg("%s.%s.%s.%d.%d.%s - %s\n", 325 test_class_name, test_name, 326 data->card_name, data->device, data->subdevice, 327 snd_pcm_stream_name(data->stream), 328 desc); 329 330 331 snd_pcm_hw_params_alloca(&hw_params); 332 snd_pcm_sw_params_alloca(&sw_params); 333 334 cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE"); 335 format = snd_pcm_format_value(cs); 336 if (format == SND_PCM_FORMAT_UNKNOWN) 337 ksft_exit_fail_msg("Wrong format '%s'\n", cs); 338 conf_get_string_array(pcm_cfg, "alt_formats", NULL, 339 alt_formats, ARRAY_SIZE(alt_formats), NULL); 340 rate = conf_get_long(pcm_cfg, "rate", NULL, 48000); 341 channels = conf_get_long(pcm_cfg, "channels", NULL, 2); 342 period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096); 343 buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384); 344 345 samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8); 346 if (!samples) 347 ksft_exit_fail_msg("Out of memory\n"); 348 snd_pcm_format_set_silence(format, samples, rate * channels); 349 350 sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice); 351 err = snd_pcm_open(&handle, name, data->stream, 0); 352 if (err < 0) { 353 snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err)); 354 goto __close; 355 } 356 357 err = snd_pcm_hw_params_any(handle, hw_params); 358 if (err < 0) { 359 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err)); 360 goto __close; 361 } 362 err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0); 363 if (err < 0) { 364 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err)); 365 goto __close; 366 } 367 err = snd_pcm_hw_params_set_access(handle, hw_params, access); 368 if (err < 0) { 369 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s", 370 snd_pcm_access_name(access), snd_strerror(err)); 371 goto __close; 372 } 373 i = -1; 374 __format: 375 err = snd_pcm_hw_params_set_format(handle, hw_params, format); 376 if (err < 0) { 377 i++; 378 if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) { 379 old_format = format; 380 format = snd_pcm_format_value(alt_formats[i]); 381 if (format != SND_PCM_FORMAT_UNKNOWN) { 382 ksft_print_msg("%s.%s.%d.%d.%s.%s format %s -> %s\n", 383 test_name, 384 data->card_name, data->device, data->subdevice, 385 snd_pcm_stream_name(data->stream), 386 snd_pcm_access_name(access), 387 snd_pcm_format_name(old_format), 388 snd_pcm_format_name(format)); 389 samples = realloc(samples, (rate * channels * 390 snd_pcm_format_physical_width(format)) / 8); 391 if (!samples) 392 ksft_exit_fail_msg("Out of memory\n"); 393 snd_pcm_format_set_silence(format, samples, rate * channels); 394 goto __format; 395 } 396 } 397 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s", 398 snd_pcm_format_name(format), snd_strerror(err)); 399 goto __close; 400 } 401 err = snd_pcm_hw_params_set_channels(handle, hw_params, channels); 402 if (err < 0) { 403 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err)); 404 goto __close; 405 } 406 rrate = rate; 407 err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0); 408 if (err < 0) { 409 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err)); 410 goto __close; 411 } 412 if (rrate != rate) { 413 snprintf(msg, sizeof(msg), "rate mismatch %ld != %u", rate, rrate); 414 goto __close; 415 } 416 rperiod_size = period_size; 417 err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0); 418 if (err < 0) { 419 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err)); 420 goto __close; 421 } 422 rbuffer_size = buffer_size; 423 err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size); 424 if (err < 0) { 425 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err)); 426 goto __close; 427 } 428 err = snd_pcm_hw_params(handle, hw_params); 429 if (err < 0) { 430 snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err)); 431 goto __close; 432 } 433 434 err = snd_pcm_sw_params_current(handle, sw_params); 435 if (err < 0) { 436 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err)); 437 goto __close; 438 } 439 if (data->stream == SND_PCM_STREAM_PLAYBACK) { 440 start_threshold = (rbuffer_size / rperiod_size) * rperiod_size; 441 } else { 442 start_threshold = rperiod_size; 443 } 444 err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold); 445 if (err < 0) { 446 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err)); 447 goto __close; 448 } 449 err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size); 450 if (err < 0) { 451 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err)); 452 goto __close; 453 } 454 err = snd_pcm_sw_params(handle, sw_params); 455 if (err < 0) { 456 snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err)); 457 goto __close; 458 } 459 460 ksft_print_msg("%s.%s.%s.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n", 461 test_class_name, test_name, 462 data->card_name, data->device, data->subdevice, 463 snd_pcm_stream_name(data->stream), 464 snd_pcm_access_name(access), 465 snd_pcm_format_name(format), 466 (long)rate, (long)channels, 467 (long)rperiod_size, (long)rbuffer_size, 468 (long)start_threshold); 469 470 /* Set all the params, actually run the test */ 471 skip = false; 472 473 timestamp_now(&tstamp); 474 for (i = 0; i < duration_s; i++) { 475 if (data->stream == SND_PCM_STREAM_PLAYBACK) { 476 frames = snd_pcm_writei(handle, samples, rate); 477 if (frames < 0) { 478 snprintf(msg, sizeof(msg), 479 "Write failed: expected %ld, wrote %li", rate, frames); 480 goto __close; 481 } 482 if (frames < rate) { 483 snprintf(msg, sizeof(msg), 484 "expected %ld, wrote %li", rate, frames); 485 goto __close; 486 } 487 } else { 488 frames = snd_pcm_readi(handle, samples, rate); 489 if (frames < 0) { 490 snprintf(msg, sizeof(msg), 491 "expected %ld, wrote %li", rate, frames); 492 goto __close; 493 } 494 if (frames < rate) { 495 snprintf(msg, sizeof(msg), 496 "expected %ld, wrote %li", rate, frames); 497 goto __close; 498 } 499 } 500 } 501 502 snd_pcm_drain(handle); 503 ms = timestamp_diff_ms(&tstamp); 504 if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) { 505 snprintf(msg, sizeof(msg), "time mismatch: expected %dms got %lld", duration_ms, ms); 506 goto __close; 507 } 508 509 msg[0] = '\0'; 510 pass = true; 511 __close: 512 pthread_mutex_lock(&results_lock); 513 514 switch (class) { 515 case TEST_CLASS_SYSTEM: 516 test_class_name = "system"; 517 /* 518 * Anything specified as specific to this system 519 * should always be supported. 520 */ 521 ksft_test_result(!skip, "%s.%s.%s.%d.%d.%s.params\n", 522 test_class_name, test_name, 523 data->card_name, data->device, 524 data->subdevice, 525 snd_pcm_stream_name(data->stream)); 526 break; 527 default: 528 break; 529 } 530 531 if (!skip) 532 ksft_test_result(pass, "%s.%s.%s.%d.%d.%s\n", 533 test_class_name, test_name, 534 data->card_name, data->device, 535 data->subdevice, 536 snd_pcm_stream_name(data->stream)); 537 else 538 ksft_test_result_skip("%s.%s.%s.%d.%d.%s\n", 539 test_class_name, test_name, 540 data->card_name, data->device, 541 data->subdevice, 542 snd_pcm_stream_name(data->stream)); 543 544 if (msg[0]) 545 ksft_print_msg("%s\n", msg); 546 547 pthread_mutex_unlock(&results_lock); 548 549 free(samples); 550 if (handle) 551 snd_pcm_close(handle); 552 } 553 554 void run_time_tests(struct pcm_data *pcm, enum test_class class, 555 snd_config_t *cfg) 556 { 557 const char *test_name, *test_type; 558 snd_config_t *pcm_cfg; 559 snd_config_iterator_t i, next; 560 561 if (!cfg) 562 return; 563 564 cfg = conf_get_subtree(cfg, "test", NULL); 565 if (cfg == NULL) 566 return; 567 568 snd_config_for_each(i, next, cfg) { 569 pcm_cfg = snd_config_iterator_entry(i); 570 if (snd_config_get_id(pcm_cfg, &test_name) < 0) 571 ksft_exit_fail_msg("snd_config_get_id\n"); 572 test_type = conf_get_string(pcm_cfg, "type", NULL, "time"); 573 if (strcmp(test_type, "time") == 0) 574 test_pcm_time(pcm, class, test_name, pcm_cfg); 575 else 576 ksft_exit_fail_msg("unknown test type '%s'\n", test_type); 577 } 578 } 579 580 void *card_thread(void *data) 581 { 582 struct card_data *card = data; 583 struct pcm_data *pcm; 584 585 for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) { 586 if (pcm->card != card->card) 587 continue; 588 589 run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config); 590 run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config); 591 } 592 593 return 0; 594 } 595 596 int main(void) 597 { 598 struct card_data *card; 599 struct card_cfg_data *conf; 600 struct pcm_data *pcm; 601 snd_config_t *global_config, *cfg; 602 int num_pcm_tests = 0, num_tests, num_std_pcm_tests; 603 int ret; 604 void *thread_ret; 605 606 ksft_print_header(); 607 608 global_config = conf_load_from_file("pcm-test.conf"); 609 default_pcm_config = conf_get_subtree(global_config, "pcm", NULL); 610 if (default_pcm_config == NULL) 611 ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n"); 612 613 conf_load(); 614 615 find_pcms(); 616 617 for (conf = conf_cards; conf; conf = conf->next) 618 if (conf->card < 0) 619 num_missing++; 620 621 num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL); 622 623 for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) { 624 num_pcm_tests += num_std_pcm_tests; 625 cfg = pcm->pcm_config; 626 if (cfg == NULL) 627 continue; 628 /* Setting params is reported as a separate test */ 629 num_tests = conf_get_count(cfg, "test", NULL) * 2; 630 if (num_tests > 0) 631 num_pcm_tests += num_tests; 632 } 633 634 ksft_set_plan(num_missing + num_pcm_tests); 635 636 for (conf = conf_cards; conf; conf = conf->next) 637 if (conf->card < 0) 638 ksft_test_result_fail("test.missing.%s.%s\n", 639 conf->filename, conf->config_id); 640 641 for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) { 642 ksft_test_result(false, "test.missing.%s.%d.%d.%s\n", 643 pcm->card_name, pcm->device, pcm->subdevice, 644 snd_pcm_stream_name(pcm->stream)); 645 } 646 647 for (card = card_list; card != NULL; card = card->next) { 648 ret = pthread_create(&card->thread, NULL, card_thread, card); 649 if (ret != 0) { 650 ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n", 651 card->card, ret, 652 strerror(errno)); 653 } 654 } 655 656 for (card = card_list; card != NULL; card = card->next) { 657 ret = pthread_join(card->thread, &thread_ret); 658 if (ret != 0) { 659 ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n", 660 card->card, ret, 661 strerror(errno)); 662 } 663 } 664 665 snd_config_delete(global_config); 666 conf_free(); 667 668 ksft_exit_pass(); 669 670 return 0; 671 } 672