672 lines · c
1// SPDX-License-Identifier: GPL-2.02//3// kselftest for the ALSA PCM API4//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, exercising9// every PCM device it can find. This may conflict with other system10// software if there is audio activity so is best run on a system with a11// 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 23typedef struct timespec timestamp_t;24 25struct 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 33struct card_data *card_list = NULL;34 35struct 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 46struct pcm_data *pcm_list = NULL;47 48int num_missing = 0;49struct pcm_data *pcm_missing = NULL;50 51snd_config_t *default_pcm_config;52 53/* Lock while reporting results since kselftest doesn't */54pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER;55 56enum test_class {57 TEST_CLASS_DEFAULT,58 TEST_CLASS_SYSTEM,59};60 61void 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 67long 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 81static 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 96static 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 123static 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 152static 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 284static 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 system519 * 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 else538 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 554void 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 else576 ksft_exit_fail_msg("unknown test type '%s'\n", test_type);577 }578}579 580void *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 596int 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