162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// kselftest for the ALSA PCM API 462306a36Sopenharmony_ci// 562306a36Sopenharmony_ci// Original author: Jaroslav Kysela <perex@perex.cz> 662306a36Sopenharmony_ci// Copyright (c) 2022 Red Hat Inc. 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci// This test will iterate over all cards detected in the system, exercising 962306a36Sopenharmony_ci// every PCM device it can find. This may conflict with other system 1062306a36Sopenharmony_ci// software if there is audio activity so is best run on a system with a 1162306a36Sopenharmony_ci// minimal active userspace. 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <stdio.h> 1462306a36Sopenharmony_ci#include <stdlib.h> 1562306a36Sopenharmony_ci#include <stdbool.h> 1662306a36Sopenharmony_ci#include <errno.h> 1762306a36Sopenharmony_ci#include <assert.h> 1862306a36Sopenharmony_ci#include <pthread.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include "../kselftest.h" 2162306a36Sopenharmony_ci#include "alsa-local.h" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_citypedef struct timespec timestamp_t; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistruct card_data { 2662306a36Sopenharmony_ci int card; 2762306a36Sopenharmony_ci pthread_t thread; 2862306a36Sopenharmony_ci struct card_data *next; 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistruct card_data *card_list = NULL; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistruct pcm_data { 3462306a36Sopenharmony_ci snd_pcm_t *handle; 3562306a36Sopenharmony_ci int card; 3662306a36Sopenharmony_ci int device; 3762306a36Sopenharmony_ci int subdevice; 3862306a36Sopenharmony_ci snd_pcm_stream_t stream; 3962306a36Sopenharmony_ci snd_config_t *pcm_config; 4062306a36Sopenharmony_ci struct pcm_data *next; 4162306a36Sopenharmony_ci}; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistruct pcm_data *pcm_list = NULL; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ciint num_missing = 0; 4662306a36Sopenharmony_cistruct pcm_data *pcm_missing = NULL; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cisnd_config_t *default_pcm_config; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci/* Lock while reporting results since kselftest doesn't */ 5162306a36Sopenharmony_cipthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cienum test_class { 5462306a36Sopenharmony_ci TEST_CLASS_DEFAULT, 5562306a36Sopenharmony_ci TEST_CLASS_SYSTEM, 5662306a36Sopenharmony_ci}; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_civoid timestamp_now(timestamp_t *tstamp) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp)) 6162306a36Sopenharmony_ci ksft_exit_fail_msg("clock_get_time\n"); 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cilong long timestamp_diff_ms(timestamp_t *tstamp) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci timestamp_t now, diff; 6762306a36Sopenharmony_ci timestamp_now(&now); 6862306a36Sopenharmony_ci if (tstamp->tv_nsec > now.tv_nsec) { 6962306a36Sopenharmony_ci diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1; 7062306a36Sopenharmony_ci diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec; 7162306a36Sopenharmony_ci } else { 7262306a36Sopenharmony_ci diff.tv_sec = now.tv_sec - tstamp->tv_sec; 7362306a36Sopenharmony_ci diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L); 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic long device_from_id(snd_config_t *node) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci const char *id; 8162306a36Sopenharmony_ci char *end; 8262306a36Sopenharmony_ci long v; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci if (snd_config_get_id(node, &id)) 8562306a36Sopenharmony_ci ksft_exit_fail_msg("snd_config_get_id\n"); 8662306a36Sopenharmony_ci errno = 0; 8762306a36Sopenharmony_ci v = strtol(id, &end, 10); 8862306a36Sopenharmony_ci if (errno || *end) 8962306a36Sopenharmony_ci return -1; 9062306a36Sopenharmony_ci return v; 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci struct pcm_data *pcm_data; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) { 9862306a36Sopenharmony_ci if (pcm_data->card != card) 9962306a36Sopenharmony_ci continue; 10062306a36Sopenharmony_ci if (pcm_data->device != device) 10162306a36Sopenharmony_ci continue; 10262306a36Sopenharmony_ci if (pcm_data->subdevice != subdevice) 10362306a36Sopenharmony_ci continue; 10462306a36Sopenharmony_ci if (pcm_data->stream != stream) 10562306a36Sopenharmony_ci continue; 10662306a36Sopenharmony_ci return; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci pcm_data = calloc(1, sizeof(*pcm_data)); 10962306a36Sopenharmony_ci if (!pcm_data) 11062306a36Sopenharmony_ci ksft_exit_fail_msg("Out of memory\n"); 11162306a36Sopenharmony_ci pcm_data->card = card; 11262306a36Sopenharmony_ci pcm_data->device = device; 11362306a36Sopenharmony_ci pcm_data->subdevice = subdevice; 11462306a36Sopenharmony_ci pcm_data->stream = stream; 11562306a36Sopenharmony_ci pcm_data->next = pcm_missing; 11662306a36Sopenharmony_ci pcm_missing = pcm_data; 11762306a36Sopenharmony_ci num_missing++; 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic void missing_devices(int card, snd_config_t *card_config) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci snd_config_t *pcm_config, *node1, *node2; 12362306a36Sopenharmony_ci snd_config_iterator_t i1, i2, next1, next2; 12462306a36Sopenharmony_ci int device, subdevice; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci pcm_config = conf_get_subtree(card_config, "pcm", NULL); 12762306a36Sopenharmony_ci if (!pcm_config) 12862306a36Sopenharmony_ci return; 12962306a36Sopenharmony_ci snd_config_for_each(i1, next1, pcm_config) { 13062306a36Sopenharmony_ci node1 = snd_config_iterator_entry(i1); 13162306a36Sopenharmony_ci device = device_from_id(node1); 13262306a36Sopenharmony_ci if (device < 0) 13362306a36Sopenharmony_ci continue; 13462306a36Sopenharmony_ci if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND) 13562306a36Sopenharmony_ci continue; 13662306a36Sopenharmony_ci snd_config_for_each(i2, next2, node1) { 13762306a36Sopenharmony_ci node2 = snd_config_iterator_entry(i2); 13862306a36Sopenharmony_ci subdevice = device_from_id(node2); 13962306a36Sopenharmony_ci if (subdevice < 0) 14062306a36Sopenharmony_ci continue; 14162306a36Sopenharmony_ci if (conf_get_subtree(node2, "PLAYBACK", NULL)) 14262306a36Sopenharmony_ci missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK); 14362306a36Sopenharmony_ci if (conf_get_subtree(node2, "CAPTURE", NULL)) 14462306a36Sopenharmony_ci missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE); 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci} 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic void find_pcms(void) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci char name[32], key[64]; 15262306a36Sopenharmony_ci char *card_name, *card_longname; 15362306a36Sopenharmony_ci int card, dev, subdev, count, direction, err; 15462306a36Sopenharmony_ci snd_pcm_stream_t stream; 15562306a36Sopenharmony_ci struct pcm_data *pcm_data; 15662306a36Sopenharmony_ci snd_ctl_t *handle; 15762306a36Sopenharmony_ci snd_pcm_info_t *pcm_info; 15862306a36Sopenharmony_ci snd_config_t *config, *card_config, *pcm_config; 15962306a36Sopenharmony_ci struct card_data *card_data; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci snd_pcm_info_alloca(&pcm_info); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci card = -1; 16462306a36Sopenharmony_ci if (snd_card_next(&card) < 0 || card < 0) 16562306a36Sopenharmony_ci return; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci config = get_alsalib_config(); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci while (card >= 0) { 17062306a36Sopenharmony_ci sprintf(name, "hw:%d", card); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci err = snd_ctl_open_lconf(&handle, name, 0, config); 17362306a36Sopenharmony_ci if (err < 0) { 17462306a36Sopenharmony_ci ksft_print_msg("Failed to get hctl for card %d: %s\n", 17562306a36Sopenharmony_ci card, snd_strerror(err)); 17662306a36Sopenharmony_ci goto next_card; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci err = snd_card_get_name(card, &card_name); 18062306a36Sopenharmony_ci if (err != 0) 18162306a36Sopenharmony_ci card_name = "Unknown"; 18262306a36Sopenharmony_ci err = snd_card_get_longname(card, &card_longname); 18362306a36Sopenharmony_ci if (err != 0) 18462306a36Sopenharmony_ci card_longname = "Unknown"; 18562306a36Sopenharmony_ci ksft_print_msg("Card %d - %s (%s)\n", card, 18662306a36Sopenharmony_ci card_name, card_longname); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci card_config = conf_by_card(card); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci card_data = calloc(1, sizeof(*card_data)); 19162306a36Sopenharmony_ci if (!card_data) 19262306a36Sopenharmony_ci ksft_exit_fail_msg("Out of memory\n"); 19362306a36Sopenharmony_ci card_data->card = card; 19462306a36Sopenharmony_ci card_data->next = card_list; 19562306a36Sopenharmony_ci card_list = card_data; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci dev = -1; 19862306a36Sopenharmony_ci while (1) { 19962306a36Sopenharmony_ci if (snd_ctl_pcm_next_device(handle, &dev) < 0) 20062306a36Sopenharmony_ci ksft_exit_fail_msg("snd_ctl_pcm_next_device\n"); 20162306a36Sopenharmony_ci if (dev < 0) 20262306a36Sopenharmony_ci break; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci for (direction = 0; direction < 2; direction++) { 20562306a36Sopenharmony_ci stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK; 20662306a36Sopenharmony_ci sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream)); 20762306a36Sopenharmony_ci pcm_config = conf_get_subtree(card_config, key, NULL); 20862306a36Sopenharmony_ci if (conf_get_bool(card_config, key, "skip", false)) { 20962306a36Sopenharmony_ci ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream)); 21062306a36Sopenharmony_ci continue; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci snd_pcm_info_set_device(pcm_info, dev); 21362306a36Sopenharmony_ci snd_pcm_info_set_subdevice(pcm_info, 0); 21462306a36Sopenharmony_ci snd_pcm_info_set_stream(pcm_info, stream); 21562306a36Sopenharmony_ci err = snd_ctl_pcm_info(handle, pcm_info); 21662306a36Sopenharmony_ci if (err == -ENOENT) 21762306a36Sopenharmony_ci continue; 21862306a36Sopenharmony_ci if (err < 0) 21962306a36Sopenharmony_ci ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n", 22062306a36Sopenharmony_ci dev, 0, stream); 22162306a36Sopenharmony_ci count = snd_pcm_info_get_subdevices_count(pcm_info); 22262306a36Sopenharmony_ci for (subdev = 0; subdev < count; subdev++) { 22362306a36Sopenharmony_ci sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream)); 22462306a36Sopenharmony_ci if (conf_get_bool(card_config, key, "skip", false)) { 22562306a36Sopenharmony_ci ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev, 22662306a36Sopenharmony_ci subdev, snd_pcm_stream_name(stream)); 22762306a36Sopenharmony_ci continue; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci pcm_data = calloc(1, sizeof(*pcm_data)); 23062306a36Sopenharmony_ci if (!pcm_data) 23162306a36Sopenharmony_ci ksft_exit_fail_msg("Out of memory\n"); 23262306a36Sopenharmony_ci pcm_data->card = card; 23362306a36Sopenharmony_ci pcm_data->device = dev; 23462306a36Sopenharmony_ci pcm_data->subdevice = subdev; 23562306a36Sopenharmony_ci pcm_data->stream = stream; 23662306a36Sopenharmony_ci pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL); 23762306a36Sopenharmony_ci pcm_data->next = pcm_list; 23862306a36Sopenharmony_ci pcm_list = pcm_data; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci /* check for missing devices */ 24462306a36Sopenharmony_ci missing_devices(card, card_config); 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci next_card: 24762306a36Sopenharmony_ci snd_ctl_close(handle); 24862306a36Sopenharmony_ci if (snd_card_next(&card) < 0) { 24962306a36Sopenharmony_ci ksft_print_msg("snd_card_next"); 25062306a36Sopenharmony_ci break; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci snd_config_delete(config); 25562306a36Sopenharmony_ci} 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_cistatic void test_pcm_time(struct pcm_data *data, enum test_class class, 25862306a36Sopenharmony_ci const char *test_name, snd_config_t *pcm_cfg) 25962306a36Sopenharmony_ci{ 26062306a36Sopenharmony_ci char name[64], msg[256]; 26162306a36Sopenharmony_ci const int duration_s = 2, margin_ms = 100; 26262306a36Sopenharmony_ci const int duration_ms = duration_s * 1000; 26362306a36Sopenharmony_ci const char *cs; 26462306a36Sopenharmony_ci int i, err; 26562306a36Sopenharmony_ci snd_pcm_t *handle = NULL; 26662306a36Sopenharmony_ci snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED; 26762306a36Sopenharmony_ci snd_pcm_format_t format, old_format; 26862306a36Sopenharmony_ci const char *alt_formats[8]; 26962306a36Sopenharmony_ci unsigned char *samples = NULL; 27062306a36Sopenharmony_ci snd_pcm_sframes_t frames; 27162306a36Sopenharmony_ci long long ms; 27262306a36Sopenharmony_ci long rate, channels, period_size, buffer_size; 27362306a36Sopenharmony_ci unsigned int rrate; 27462306a36Sopenharmony_ci snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold; 27562306a36Sopenharmony_ci timestamp_t tstamp; 27662306a36Sopenharmony_ci bool pass = false; 27762306a36Sopenharmony_ci snd_pcm_hw_params_t *hw_params; 27862306a36Sopenharmony_ci snd_pcm_sw_params_t *sw_params; 27962306a36Sopenharmony_ci const char *test_class_name; 28062306a36Sopenharmony_ci bool skip = true; 28162306a36Sopenharmony_ci const char *desc; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci switch (class) { 28462306a36Sopenharmony_ci case TEST_CLASS_DEFAULT: 28562306a36Sopenharmony_ci test_class_name = "default"; 28662306a36Sopenharmony_ci break; 28762306a36Sopenharmony_ci case TEST_CLASS_SYSTEM: 28862306a36Sopenharmony_ci test_class_name = "system"; 28962306a36Sopenharmony_ci break; 29062306a36Sopenharmony_ci default: 29162306a36Sopenharmony_ci ksft_exit_fail_msg("Unknown test class %d\n", class); 29262306a36Sopenharmony_ci break; 29362306a36Sopenharmony_ci } 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci desc = conf_get_string(pcm_cfg, "description", NULL, NULL); 29662306a36Sopenharmony_ci if (desc) 29762306a36Sopenharmony_ci ksft_print_msg("%s.%s.%d.%d.%d.%s - %s\n", 29862306a36Sopenharmony_ci test_class_name, test_name, 29962306a36Sopenharmony_ci data->card, data->device, data->subdevice, 30062306a36Sopenharmony_ci snd_pcm_stream_name(data->stream), 30162306a36Sopenharmony_ci desc); 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci snd_pcm_hw_params_alloca(&hw_params); 30562306a36Sopenharmony_ci snd_pcm_sw_params_alloca(&sw_params); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE"); 30862306a36Sopenharmony_ci format = snd_pcm_format_value(cs); 30962306a36Sopenharmony_ci if (format == SND_PCM_FORMAT_UNKNOWN) 31062306a36Sopenharmony_ci ksft_exit_fail_msg("Wrong format '%s'\n", cs); 31162306a36Sopenharmony_ci conf_get_string_array(pcm_cfg, "alt_formats", NULL, 31262306a36Sopenharmony_ci alt_formats, ARRAY_SIZE(alt_formats), NULL); 31362306a36Sopenharmony_ci rate = conf_get_long(pcm_cfg, "rate", NULL, 48000); 31462306a36Sopenharmony_ci channels = conf_get_long(pcm_cfg, "channels", NULL, 2); 31562306a36Sopenharmony_ci period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096); 31662306a36Sopenharmony_ci buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8); 31962306a36Sopenharmony_ci if (!samples) 32062306a36Sopenharmony_ci ksft_exit_fail_msg("Out of memory\n"); 32162306a36Sopenharmony_ci snd_pcm_format_set_silence(format, samples, rate * channels); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice); 32462306a36Sopenharmony_ci err = snd_pcm_open(&handle, name, data->stream, 0); 32562306a36Sopenharmony_ci if (err < 0) { 32662306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err)); 32762306a36Sopenharmony_ci goto __close; 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci err = snd_pcm_hw_params_any(handle, hw_params); 33162306a36Sopenharmony_ci if (err < 0) { 33262306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err)); 33362306a36Sopenharmony_ci goto __close; 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0); 33662306a36Sopenharmony_ci if (err < 0) { 33762306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err)); 33862306a36Sopenharmony_ci goto __close; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci err = snd_pcm_hw_params_set_access(handle, hw_params, access); 34162306a36Sopenharmony_ci if (err < 0) { 34262306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s", 34362306a36Sopenharmony_ci snd_pcm_access_name(access), snd_strerror(err)); 34462306a36Sopenharmony_ci goto __close; 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci i = -1; 34762306a36Sopenharmony_ci__format: 34862306a36Sopenharmony_ci err = snd_pcm_hw_params_set_format(handle, hw_params, format); 34962306a36Sopenharmony_ci if (err < 0) { 35062306a36Sopenharmony_ci i++; 35162306a36Sopenharmony_ci if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) { 35262306a36Sopenharmony_ci old_format = format; 35362306a36Sopenharmony_ci format = snd_pcm_format_value(alt_formats[i]); 35462306a36Sopenharmony_ci if (format != SND_PCM_FORMAT_UNKNOWN) { 35562306a36Sopenharmony_ci ksft_print_msg("%s.%d.%d.%d.%s.%s format %s -> %s\n", 35662306a36Sopenharmony_ci test_name, 35762306a36Sopenharmony_ci data->card, data->device, data->subdevice, 35862306a36Sopenharmony_ci snd_pcm_stream_name(data->stream), 35962306a36Sopenharmony_ci snd_pcm_access_name(access), 36062306a36Sopenharmony_ci snd_pcm_format_name(old_format), 36162306a36Sopenharmony_ci snd_pcm_format_name(format)); 36262306a36Sopenharmony_ci samples = realloc(samples, (rate * channels * 36362306a36Sopenharmony_ci snd_pcm_format_physical_width(format)) / 8); 36462306a36Sopenharmony_ci if (!samples) 36562306a36Sopenharmony_ci ksft_exit_fail_msg("Out of memory\n"); 36662306a36Sopenharmony_ci snd_pcm_format_set_silence(format, samples, rate * channels); 36762306a36Sopenharmony_ci goto __format; 36862306a36Sopenharmony_ci } 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s", 37162306a36Sopenharmony_ci snd_pcm_format_name(format), snd_strerror(err)); 37262306a36Sopenharmony_ci goto __close; 37362306a36Sopenharmony_ci } 37462306a36Sopenharmony_ci err = snd_pcm_hw_params_set_channels(handle, hw_params, channels); 37562306a36Sopenharmony_ci if (err < 0) { 37662306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err)); 37762306a36Sopenharmony_ci goto __close; 37862306a36Sopenharmony_ci } 37962306a36Sopenharmony_ci rrate = rate; 38062306a36Sopenharmony_ci err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0); 38162306a36Sopenharmony_ci if (err < 0) { 38262306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err)); 38362306a36Sopenharmony_ci goto __close; 38462306a36Sopenharmony_ci } 38562306a36Sopenharmony_ci if (rrate != rate) { 38662306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "rate mismatch %ld != %d", rate, rrate); 38762306a36Sopenharmony_ci goto __close; 38862306a36Sopenharmony_ci } 38962306a36Sopenharmony_ci rperiod_size = period_size; 39062306a36Sopenharmony_ci err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0); 39162306a36Sopenharmony_ci if (err < 0) { 39262306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err)); 39362306a36Sopenharmony_ci goto __close; 39462306a36Sopenharmony_ci } 39562306a36Sopenharmony_ci rbuffer_size = buffer_size; 39662306a36Sopenharmony_ci err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size); 39762306a36Sopenharmony_ci if (err < 0) { 39862306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err)); 39962306a36Sopenharmony_ci goto __close; 40062306a36Sopenharmony_ci } 40162306a36Sopenharmony_ci err = snd_pcm_hw_params(handle, hw_params); 40262306a36Sopenharmony_ci if (err < 0) { 40362306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err)); 40462306a36Sopenharmony_ci goto __close; 40562306a36Sopenharmony_ci } 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci err = snd_pcm_sw_params_current(handle, sw_params); 40862306a36Sopenharmony_ci if (err < 0) { 40962306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err)); 41062306a36Sopenharmony_ci goto __close; 41162306a36Sopenharmony_ci } 41262306a36Sopenharmony_ci if (data->stream == SND_PCM_STREAM_PLAYBACK) { 41362306a36Sopenharmony_ci start_threshold = (rbuffer_size / rperiod_size) * rperiod_size; 41462306a36Sopenharmony_ci } else { 41562306a36Sopenharmony_ci start_threshold = rperiod_size; 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold); 41862306a36Sopenharmony_ci if (err < 0) { 41962306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err)); 42062306a36Sopenharmony_ci goto __close; 42162306a36Sopenharmony_ci } 42262306a36Sopenharmony_ci err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size); 42362306a36Sopenharmony_ci if (err < 0) { 42462306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err)); 42562306a36Sopenharmony_ci goto __close; 42662306a36Sopenharmony_ci } 42762306a36Sopenharmony_ci err = snd_pcm_sw_params(handle, sw_params); 42862306a36Sopenharmony_ci if (err < 0) { 42962306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err)); 43062306a36Sopenharmony_ci goto __close; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci ksft_print_msg("%s.%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n", 43462306a36Sopenharmony_ci test_class_name, test_name, 43562306a36Sopenharmony_ci data->card, data->device, data->subdevice, 43662306a36Sopenharmony_ci snd_pcm_stream_name(data->stream), 43762306a36Sopenharmony_ci snd_pcm_access_name(access), 43862306a36Sopenharmony_ci snd_pcm_format_name(format), 43962306a36Sopenharmony_ci (long)rate, (long)channels, 44062306a36Sopenharmony_ci (long)rperiod_size, (long)rbuffer_size, 44162306a36Sopenharmony_ci (long)start_threshold); 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci /* Set all the params, actually run the test */ 44462306a36Sopenharmony_ci skip = false; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci timestamp_now(&tstamp); 44762306a36Sopenharmony_ci for (i = 0; i < duration_s; i++) { 44862306a36Sopenharmony_ci if (data->stream == SND_PCM_STREAM_PLAYBACK) { 44962306a36Sopenharmony_ci frames = snd_pcm_writei(handle, samples, rate); 45062306a36Sopenharmony_ci if (frames < 0) { 45162306a36Sopenharmony_ci snprintf(msg, sizeof(msg), 45262306a36Sopenharmony_ci "Write failed: expected %ld, wrote %li", rate, frames); 45362306a36Sopenharmony_ci goto __close; 45462306a36Sopenharmony_ci } 45562306a36Sopenharmony_ci if (frames < rate) { 45662306a36Sopenharmony_ci snprintf(msg, sizeof(msg), 45762306a36Sopenharmony_ci "expected %ld, wrote %li", rate, frames); 45862306a36Sopenharmony_ci goto __close; 45962306a36Sopenharmony_ci } 46062306a36Sopenharmony_ci } else { 46162306a36Sopenharmony_ci frames = snd_pcm_readi(handle, samples, rate); 46262306a36Sopenharmony_ci if (frames < 0) { 46362306a36Sopenharmony_ci snprintf(msg, sizeof(msg), 46462306a36Sopenharmony_ci "expected %ld, wrote %li", rate, frames); 46562306a36Sopenharmony_ci goto __close; 46662306a36Sopenharmony_ci } 46762306a36Sopenharmony_ci if (frames < rate) { 46862306a36Sopenharmony_ci snprintf(msg, sizeof(msg), 46962306a36Sopenharmony_ci "expected %ld, wrote %li", rate, frames); 47062306a36Sopenharmony_ci goto __close; 47162306a36Sopenharmony_ci } 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci } 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci snd_pcm_drain(handle); 47662306a36Sopenharmony_ci ms = timestamp_diff_ms(&tstamp); 47762306a36Sopenharmony_ci if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) { 47862306a36Sopenharmony_ci snprintf(msg, sizeof(msg), "time mismatch: expected %dms got %lld", duration_ms, ms); 47962306a36Sopenharmony_ci goto __close; 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci msg[0] = '\0'; 48362306a36Sopenharmony_ci pass = true; 48462306a36Sopenharmony_ci__close: 48562306a36Sopenharmony_ci pthread_mutex_lock(&results_lock); 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci switch (class) { 48862306a36Sopenharmony_ci case TEST_CLASS_SYSTEM: 48962306a36Sopenharmony_ci test_class_name = "system"; 49062306a36Sopenharmony_ci /* 49162306a36Sopenharmony_ci * Anything specified as specific to this system 49262306a36Sopenharmony_ci * should always be supported. 49362306a36Sopenharmony_ci */ 49462306a36Sopenharmony_ci ksft_test_result(!skip, "%s.%s.%d.%d.%d.%s.params\n", 49562306a36Sopenharmony_ci test_class_name, test_name, 49662306a36Sopenharmony_ci data->card, data->device, data->subdevice, 49762306a36Sopenharmony_ci snd_pcm_stream_name(data->stream)); 49862306a36Sopenharmony_ci break; 49962306a36Sopenharmony_ci default: 50062306a36Sopenharmony_ci break; 50162306a36Sopenharmony_ci } 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci if (!skip) 50462306a36Sopenharmony_ci ksft_test_result(pass, "%s.%s.%d.%d.%d.%s\n", 50562306a36Sopenharmony_ci test_class_name, test_name, 50662306a36Sopenharmony_ci data->card, data->device, data->subdevice, 50762306a36Sopenharmony_ci snd_pcm_stream_name(data->stream)); 50862306a36Sopenharmony_ci else 50962306a36Sopenharmony_ci ksft_test_result_skip("%s.%s.%d.%d.%d.%s\n", 51062306a36Sopenharmony_ci test_class_name, test_name, 51162306a36Sopenharmony_ci data->card, data->device, data->subdevice, 51262306a36Sopenharmony_ci snd_pcm_stream_name(data->stream)); 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci if (msg[0]) 51562306a36Sopenharmony_ci ksft_print_msg("%s\n", msg); 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci pthread_mutex_unlock(&results_lock); 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci free(samples); 52062306a36Sopenharmony_ci if (handle) 52162306a36Sopenharmony_ci snd_pcm_close(handle); 52262306a36Sopenharmony_ci} 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_civoid run_time_tests(struct pcm_data *pcm, enum test_class class, 52562306a36Sopenharmony_ci snd_config_t *cfg) 52662306a36Sopenharmony_ci{ 52762306a36Sopenharmony_ci const char *test_name, *test_type; 52862306a36Sopenharmony_ci snd_config_t *pcm_cfg; 52962306a36Sopenharmony_ci snd_config_iterator_t i, next; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci if (!cfg) 53262306a36Sopenharmony_ci return; 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci cfg = conf_get_subtree(cfg, "test", NULL); 53562306a36Sopenharmony_ci if (cfg == NULL) 53662306a36Sopenharmony_ci return; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci snd_config_for_each(i, next, cfg) { 53962306a36Sopenharmony_ci pcm_cfg = snd_config_iterator_entry(i); 54062306a36Sopenharmony_ci if (snd_config_get_id(pcm_cfg, &test_name) < 0) 54162306a36Sopenharmony_ci ksft_exit_fail_msg("snd_config_get_id\n"); 54262306a36Sopenharmony_ci test_type = conf_get_string(pcm_cfg, "type", NULL, "time"); 54362306a36Sopenharmony_ci if (strcmp(test_type, "time") == 0) 54462306a36Sopenharmony_ci test_pcm_time(pcm, class, test_name, pcm_cfg); 54562306a36Sopenharmony_ci else 54662306a36Sopenharmony_ci ksft_exit_fail_msg("unknown test type '%s'\n", test_type); 54762306a36Sopenharmony_ci } 54862306a36Sopenharmony_ci} 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_civoid *card_thread(void *data) 55162306a36Sopenharmony_ci{ 55262306a36Sopenharmony_ci struct card_data *card = data; 55362306a36Sopenharmony_ci struct pcm_data *pcm; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) { 55662306a36Sopenharmony_ci if (pcm->card != card->card) 55762306a36Sopenharmony_ci continue; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config); 56062306a36Sopenharmony_ci run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config); 56162306a36Sopenharmony_ci } 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci return 0; 56462306a36Sopenharmony_ci} 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ciint main(void) 56762306a36Sopenharmony_ci{ 56862306a36Sopenharmony_ci struct card_data *card; 56962306a36Sopenharmony_ci struct pcm_data *pcm; 57062306a36Sopenharmony_ci snd_config_t *global_config, *cfg; 57162306a36Sopenharmony_ci int num_pcm_tests = 0, num_tests, num_std_pcm_tests; 57262306a36Sopenharmony_ci int ret; 57362306a36Sopenharmony_ci void *thread_ret; 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci ksft_print_header(); 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci global_config = conf_load_from_file("pcm-test.conf"); 57862306a36Sopenharmony_ci default_pcm_config = conf_get_subtree(global_config, "pcm", NULL); 57962306a36Sopenharmony_ci if (default_pcm_config == NULL) 58062306a36Sopenharmony_ci ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n"); 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci conf_load(); 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci find_pcms(); 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL); 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) { 58962306a36Sopenharmony_ci num_pcm_tests += num_std_pcm_tests; 59062306a36Sopenharmony_ci cfg = pcm->pcm_config; 59162306a36Sopenharmony_ci if (cfg == NULL) 59262306a36Sopenharmony_ci continue; 59362306a36Sopenharmony_ci /* Setting params is reported as a separate test */ 59462306a36Sopenharmony_ci num_tests = conf_get_count(cfg, "test", NULL) * 2; 59562306a36Sopenharmony_ci if (num_tests > 0) 59662306a36Sopenharmony_ci num_pcm_tests += num_tests; 59762306a36Sopenharmony_ci } 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci ksft_set_plan(num_missing + num_pcm_tests); 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) { 60262306a36Sopenharmony_ci ksft_test_result(false, "test.missing.%d.%d.%d.%s\n", 60362306a36Sopenharmony_ci pcm->card, pcm->device, pcm->subdevice, 60462306a36Sopenharmony_ci snd_pcm_stream_name(pcm->stream)); 60562306a36Sopenharmony_ci } 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci for (card = card_list; card != NULL; card = card->next) { 60862306a36Sopenharmony_ci ret = pthread_create(&card->thread, NULL, card_thread, card); 60962306a36Sopenharmony_ci if (ret != 0) { 61062306a36Sopenharmony_ci ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n", 61162306a36Sopenharmony_ci card->card, ret, 61262306a36Sopenharmony_ci strerror(errno)); 61362306a36Sopenharmony_ci } 61462306a36Sopenharmony_ci } 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci for (card = card_list; card != NULL; card = card->next) { 61762306a36Sopenharmony_ci ret = pthread_join(card->thread, &thread_ret); 61862306a36Sopenharmony_ci if (ret != 0) { 61962306a36Sopenharmony_ci ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n", 62062306a36Sopenharmony_ci card->card, ret, 62162306a36Sopenharmony_ci strerror(errno)); 62262306a36Sopenharmony_ci } 62362306a36Sopenharmony_ci } 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci snd_config_delete(global_config); 62662306a36Sopenharmony_ci conf_free(); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci ksft_exit_pass(); 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci return 0; 63162306a36Sopenharmony_ci} 632