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