1aba51cd0SJaroslav Kysela // SPDX-License-Identifier: GPL-2.0 2aba51cd0SJaroslav Kysela // 3aba51cd0SJaroslav Kysela // kselftest for the ALSA PCM API 4aba51cd0SJaroslav Kysela // 5aba51cd0SJaroslav Kysela // Original author: Jaroslav Kysela <perex@perex.cz> 6aba51cd0SJaroslav Kysela // Copyright (c) 2022 Red Hat Inc. 7aba51cd0SJaroslav Kysela 8aba51cd0SJaroslav Kysela // This test will iterate over all cards detected in the system, exercising 9aba51cd0SJaroslav Kysela // every PCM device it can find. This may conflict with other system 10aba51cd0SJaroslav Kysela // software if there is audio activity so is best run on a system with a 11aba51cd0SJaroslav Kysela // minimal active userspace. 12aba51cd0SJaroslav Kysela 13aba51cd0SJaroslav Kysela #include <stdio.h> 14aba51cd0SJaroslav Kysela #include <stdlib.h> 15aba51cd0SJaroslav Kysela #include <stdbool.h> 16aba51cd0SJaroslav Kysela #include <errno.h> 17aba51cd0SJaroslav Kysela #include <assert.h> 1869218b59SMark Brown #include <pthread.h> 19aba51cd0SJaroslav Kysela 20aba51cd0SJaroslav Kysela #include "../kselftest.h" 21aba51cd0SJaroslav Kysela #include "alsa-local.h" 22aba51cd0SJaroslav Kysela 23aba51cd0SJaroslav Kysela typedef struct timespec timestamp_t; 24aba51cd0SJaroslav Kysela 2569218b59SMark Brown struct card_data { 2669218b59SMark Brown int card; 2769218b59SMark Brown pthread_t thread; 2869218b59SMark Brown struct card_data *next; 2969218b59SMark Brown }; 3069218b59SMark Brown 3169218b59SMark Brown struct card_data *card_list = NULL; 3269218b59SMark Brown 33aba51cd0SJaroslav Kysela struct pcm_data { 34aba51cd0SJaroslav Kysela snd_pcm_t *handle; 35aba51cd0SJaroslav Kysela int card; 36aba51cd0SJaroslav Kysela int device; 37aba51cd0SJaroslav Kysela int subdevice; 38aba51cd0SJaroslav Kysela snd_pcm_stream_t stream; 39aba51cd0SJaroslav Kysela snd_config_t *pcm_config; 40aba51cd0SJaroslav Kysela struct pcm_data *next; 41aba51cd0SJaroslav Kysela }; 42aba51cd0SJaroslav Kysela 43aba51cd0SJaroslav Kysela struct pcm_data *pcm_list = NULL; 44aba51cd0SJaroslav Kysela 45aba51cd0SJaroslav Kysela int num_missing = 0; 46aba51cd0SJaroslav Kysela struct pcm_data *pcm_missing = NULL; 47aba51cd0SJaroslav Kysela 4869218b59SMark Brown snd_config_t *default_pcm_config; 4969218b59SMark Brown 5069218b59SMark Brown /* Lock while reporting results since kselftest doesn't */ 5169218b59SMark Brown pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER; 5269218b59SMark Brown 537769f1abSMark Brown enum test_class { 547769f1abSMark Brown TEST_CLASS_DEFAULT, 557769f1abSMark Brown TEST_CLASS_SYSTEM, 567769f1abSMark Brown }; 577769f1abSMark Brown 58aba51cd0SJaroslav Kysela void timestamp_now(timestamp_t *tstamp) 59aba51cd0SJaroslav Kysela { 60aba51cd0SJaroslav Kysela if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp)) 61aba51cd0SJaroslav Kysela ksft_exit_fail_msg("clock_get_time\n"); 62aba51cd0SJaroslav Kysela } 63aba51cd0SJaroslav Kysela 64aba51cd0SJaroslav Kysela long long timestamp_diff_ms(timestamp_t *tstamp) 65aba51cd0SJaroslav Kysela { 66aba51cd0SJaroslav Kysela timestamp_t now, diff; 67aba51cd0SJaroslav Kysela timestamp_now(&now); 68aba51cd0SJaroslav Kysela if (tstamp->tv_nsec > now.tv_nsec) { 69aba51cd0SJaroslav Kysela diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1; 70aba51cd0SJaroslav Kysela diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec; 71aba51cd0SJaroslav Kysela } else { 72aba51cd0SJaroslav Kysela diff.tv_sec = now.tv_sec - tstamp->tv_sec; 73aba51cd0SJaroslav Kysela diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec; 74aba51cd0SJaroslav Kysela } 75aba51cd0SJaroslav Kysela return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L); 76aba51cd0SJaroslav Kysela } 77aba51cd0SJaroslav Kysela 78aba51cd0SJaroslav Kysela static long device_from_id(snd_config_t *node) 79aba51cd0SJaroslav Kysela { 80aba51cd0SJaroslav Kysela const char *id; 81aba51cd0SJaroslav Kysela char *end; 82aba51cd0SJaroslav Kysela long v; 83aba51cd0SJaroslav Kysela 84aba51cd0SJaroslav Kysela if (snd_config_get_id(node, &id)) 85aba51cd0SJaroslav Kysela ksft_exit_fail_msg("snd_config_get_id\n"); 86aba51cd0SJaroslav Kysela errno = 0; 87aba51cd0SJaroslav Kysela v = strtol(id, &end, 10); 88aba51cd0SJaroslav Kysela if (errno || *end) 89aba51cd0SJaroslav Kysela return -1; 90aba51cd0SJaroslav Kysela return v; 91aba51cd0SJaroslav Kysela } 92aba51cd0SJaroslav Kysela 93aba51cd0SJaroslav Kysela static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream) 94aba51cd0SJaroslav Kysela { 95aba51cd0SJaroslav Kysela struct pcm_data *pcm_data; 96aba51cd0SJaroslav Kysela 97aba51cd0SJaroslav Kysela for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) { 98aba51cd0SJaroslav Kysela if (pcm_data->card != card) 99aba51cd0SJaroslav Kysela continue; 100aba51cd0SJaroslav Kysela if (pcm_data->device != device) 101aba51cd0SJaroslav Kysela continue; 102aba51cd0SJaroslav Kysela if (pcm_data->subdevice != subdevice) 103aba51cd0SJaroslav Kysela continue; 104aba51cd0SJaroslav Kysela if (pcm_data->stream != stream) 105aba51cd0SJaroslav Kysela continue; 106aba51cd0SJaroslav Kysela return; 107aba51cd0SJaroslav Kysela } 108aba51cd0SJaroslav Kysela pcm_data = calloc(1, sizeof(*pcm_data)); 109aba51cd0SJaroslav Kysela if (!pcm_data) 110aba51cd0SJaroslav Kysela ksft_exit_fail_msg("Out of memory\n"); 111aba51cd0SJaroslav Kysela pcm_data->card = card; 112aba51cd0SJaroslav Kysela pcm_data->device = device; 113aba51cd0SJaroslav Kysela pcm_data->subdevice = subdevice; 114aba51cd0SJaroslav Kysela pcm_data->stream = stream; 115aba51cd0SJaroslav Kysela pcm_data->next = pcm_missing; 116aba51cd0SJaroslav Kysela pcm_missing = pcm_data; 117aba51cd0SJaroslav Kysela num_missing++; 118aba51cd0SJaroslav Kysela } 119aba51cd0SJaroslav Kysela 120aba51cd0SJaroslav Kysela static void missing_devices(int card, snd_config_t *card_config) 121aba51cd0SJaroslav Kysela { 122aba51cd0SJaroslav Kysela snd_config_t *pcm_config, *node1, *node2; 123aba51cd0SJaroslav Kysela snd_config_iterator_t i1, i2, next1, next2; 124aba51cd0SJaroslav Kysela int device, subdevice; 125aba51cd0SJaroslav Kysela 126aba51cd0SJaroslav Kysela pcm_config = conf_get_subtree(card_config, "pcm", NULL); 127aba51cd0SJaroslav Kysela if (!pcm_config) 128aba51cd0SJaroslav Kysela return; 129aba51cd0SJaroslav Kysela snd_config_for_each(i1, next1, pcm_config) { 130aba51cd0SJaroslav Kysela node1 = snd_config_iterator_entry(i1); 131aba51cd0SJaroslav Kysela device = device_from_id(node1); 132aba51cd0SJaroslav Kysela if (device < 0) 133aba51cd0SJaroslav Kysela continue; 134aba51cd0SJaroslav Kysela if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND) 135aba51cd0SJaroslav Kysela continue; 136aba51cd0SJaroslav Kysela snd_config_for_each(i2, next2, node1) { 137aba51cd0SJaroslav Kysela node2 = snd_config_iterator_entry(i2); 138aba51cd0SJaroslav Kysela subdevice = device_from_id(node2); 139aba51cd0SJaroslav Kysela if (subdevice < 0) 140aba51cd0SJaroslav Kysela continue; 141aba51cd0SJaroslav Kysela if (conf_get_subtree(node2, "PLAYBACK", NULL)) 142aba51cd0SJaroslav Kysela missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK); 143aba51cd0SJaroslav Kysela if (conf_get_subtree(node2, "CAPTURE", NULL)) 144aba51cd0SJaroslav Kysela missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE); 145aba51cd0SJaroslav Kysela } 146aba51cd0SJaroslav Kysela } 147aba51cd0SJaroslav Kysela } 148aba51cd0SJaroslav Kysela 149aba51cd0SJaroslav Kysela static void find_pcms(void) 150aba51cd0SJaroslav Kysela { 151aba51cd0SJaroslav Kysela char name[32], key[64]; 1521a0cc052SMark Brown char *card_name, *card_longname; 153aba51cd0SJaroslav Kysela int card, dev, subdev, count, direction, err; 154aba51cd0SJaroslav Kysela snd_pcm_stream_t stream; 155aba51cd0SJaroslav Kysela struct pcm_data *pcm_data; 156aba51cd0SJaroslav Kysela snd_ctl_t *handle; 157aba51cd0SJaroslav Kysela snd_pcm_info_t *pcm_info; 158aba51cd0SJaroslav Kysela snd_config_t *config, *card_config, *pcm_config; 15969218b59SMark Brown struct card_data *card_data; 160aba51cd0SJaroslav Kysela 161aba51cd0SJaroslav Kysela snd_pcm_info_alloca(&pcm_info); 162aba51cd0SJaroslav Kysela 163aba51cd0SJaroslav Kysela card = -1; 164aba51cd0SJaroslav Kysela if (snd_card_next(&card) < 0 || card < 0) 165aba51cd0SJaroslav Kysela return; 166aba51cd0SJaroslav Kysela 167aba51cd0SJaroslav Kysela config = get_alsalib_config(); 168aba51cd0SJaroslav Kysela 169aba51cd0SJaroslav Kysela while (card >= 0) { 170aba51cd0SJaroslav Kysela sprintf(name, "hw:%d", card); 171aba51cd0SJaroslav Kysela 172aba51cd0SJaroslav Kysela err = snd_ctl_open_lconf(&handle, name, 0, config); 173aba51cd0SJaroslav Kysela if (err < 0) { 174aba51cd0SJaroslav Kysela ksft_print_msg("Failed to get hctl for card %d: %s\n", 175aba51cd0SJaroslav Kysela card, snd_strerror(err)); 176aba51cd0SJaroslav Kysela goto next_card; 177aba51cd0SJaroslav Kysela } 178aba51cd0SJaroslav Kysela 1791a0cc052SMark Brown err = snd_card_get_name(card, &card_name); 1801a0cc052SMark Brown if (err != 0) 1811a0cc052SMark Brown card_name = "Unknown"; 1821a0cc052SMark Brown err = snd_card_get_longname(card, &card_longname); 1831a0cc052SMark Brown if (err != 0) 1841a0cc052SMark Brown card_longname = "Unknown"; 1851a0cc052SMark Brown ksft_print_msg("Card %d - %s (%s)\n", card, 1861a0cc052SMark Brown card_name, card_longname); 1871a0cc052SMark Brown 188aba51cd0SJaroslav Kysela card_config = conf_by_card(card); 189aba51cd0SJaroslav Kysela 19069218b59SMark Brown card_data = calloc(1, sizeof(*card_data)); 19169218b59SMark Brown if (!card_data) 19269218b59SMark Brown ksft_exit_fail_msg("Out of memory\n"); 19369218b59SMark Brown card_data->card = card; 19469218b59SMark Brown card_data->next = card_list; 19569218b59SMark Brown card_list = card_data; 19669218b59SMark Brown 197aba51cd0SJaroslav Kysela dev = -1; 198aba51cd0SJaroslav Kysela while (1) { 199aba51cd0SJaroslav Kysela if (snd_ctl_pcm_next_device(handle, &dev) < 0) 200aba51cd0SJaroslav Kysela ksft_exit_fail_msg("snd_ctl_pcm_next_device\n"); 201aba51cd0SJaroslav Kysela if (dev < 0) 202aba51cd0SJaroslav Kysela break; 203aba51cd0SJaroslav Kysela 204aba51cd0SJaroslav Kysela for (direction = 0; direction < 2; direction++) { 205aba51cd0SJaroslav Kysela stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK; 206aba51cd0SJaroslav Kysela sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream)); 207aba51cd0SJaroslav Kysela pcm_config = conf_get_subtree(card_config, key, NULL); 208aba51cd0SJaroslav Kysela if (conf_get_bool(card_config, key, "skip", false)) { 209aba51cd0SJaroslav Kysela ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream)); 210aba51cd0SJaroslav Kysela continue; 211aba51cd0SJaroslav Kysela } 212aba51cd0SJaroslav Kysela snd_pcm_info_set_device(pcm_info, dev); 213aba51cd0SJaroslav Kysela snd_pcm_info_set_subdevice(pcm_info, 0); 214aba51cd0SJaroslav Kysela snd_pcm_info_set_stream(pcm_info, stream); 215aba51cd0SJaroslav Kysela err = snd_ctl_pcm_info(handle, pcm_info); 216aba51cd0SJaroslav Kysela if (err == -ENOENT) 217aba51cd0SJaroslav Kysela continue; 218aba51cd0SJaroslav Kysela if (err < 0) 219aba51cd0SJaroslav Kysela ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n", 220aba51cd0SJaroslav Kysela dev, 0, stream); 221aba51cd0SJaroslav Kysela count = snd_pcm_info_get_subdevices_count(pcm_info); 222aba51cd0SJaroslav Kysela for (subdev = 0; subdev < count; subdev++) { 223aba51cd0SJaroslav Kysela sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream)); 224aba51cd0SJaroslav Kysela if (conf_get_bool(card_config, key, "skip", false)) { 225aba51cd0SJaroslav Kysela ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev, 226aba51cd0SJaroslav Kysela subdev, snd_pcm_stream_name(stream)); 227aba51cd0SJaroslav Kysela continue; 228aba51cd0SJaroslav Kysela } 229aba51cd0SJaroslav Kysela pcm_data = calloc(1, sizeof(*pcm_data)); 230aba51cd0SJaroslav Kysela if (!pcm_data) 231aba51cd0SJaroslav Kysela ksft_exit_fail_msg("Out of memory\n"); 232aba51cd0SJaroslav Kysela pcm_data->card = card; 233aba51cd0SJaroslav Kysela pcm_data->device = dev; 234aba51cd0SJaroslav Kysela pcm_data->subdevice = subdev; 235aba51cd0SJaroslav Kysela pcm_data->stream = stream; 236aba51cd0SJaroslav Kysela pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL); 237aba51cd0SJaroslav Kysela pcm_data->next = pcm_list; 238aba51cd0SJaroslav Kysela pcm_list = pcm_data; 239aba51cd0SJaroslav Kysela } 240aba51cd0SJaroslav Kysela } 241aba51cd0SJaroslav Kysela } 242aba51cd0SJaroslav Kysela 243aba51cd0SJaroslav Kysela /* check for missing devices */ 244aba51cd0SJaroslav Kysela missing_devices(card, card_config); 245aba51cd0SJaroslav Kysela 246aba51cd0SJaroslav Kysela next_card: 247aba51cd0SJaroslav Kysela snd_ctl_close(handle); 248aba51cd0SJaroslav Kysela if (snd_card_next(&card) < 0) { 249aba51cd0SJaroslav Kysela ksft_print_msg("snd_card_next"); 250aba51cd0SJaroslav Kysela break; 251aba51cd0SJaroslav Kysela } 252aba51cd0SJaroslav Kysela } 253aba51cd0SJaroslav Kysela 254aba51cd0SJaroslav Kysela snd_config_delete(config); 255aba51cd0SJaroslav Kysela } 256aba51cd0SJaroslav Kysela 2577769f1abSMark Brown static void test_pcm_time(struct pcm_data *data, enum test_class class, 2587769f1abSMark Brown const char *test_name, snd_config_t *pcm_cfg) 259aba51cd0SJaroslav Kysela { 260aba51cd0SJaroslav Kysela char name[64], key[128], msg[256]; 261*0825d54aSNícolas F. R. A. Prado const int duration_s = 4, margin_ms = 100; 262*0825d54aSNícolas F. R. A. Prado const int duration_ms = duration_s * 1000; 263aba51cd0SJaroslav Kysela const char *cs; 264aba51cd0SJaroslav Kysela int i, err; 265aba51cd0SJaroslav Kysela snd_pcm_t *handle = NULL; 266aba51cd0SJaroslav Kysela snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED; 267348d09fcSJaroslav Kysela snd_pcm_format_t format, old_format; 268348d09fcSJaroslav Kysela const char *alt_formats[8]; 269aba51cd0SJaroslav Kysela unsigned char *samples = NULL; 270aba51cd0SJaroslav Kysela snd_pcm_sframes_t frames; 271aba51cd0SJaroslav Kysela long long ms; 272aba51cd0SJaroslav Kysela long rate, channels, period_size, buffer_size; 273aba51cd0SJaroslav Kysela unsigned int rrate; 274aba51cd0SJaroslav Kysela snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold; 275aba51cd0SJaroslav Kysela timestamp_t tstamp; 276348d09fcSJaroslav Kysela bool pass = false; 277aba51cd0SJaroslav Kysela snd_pcm_hw_params_t *hw_params; 278aba51cd0SJaroslav Kysela snd_pcm_sw_params_t *sw_params; 2797769f1abSMark Brown const char *test_class_name; 28034fb956cSMark Brown bool skip = true; 2818acb4524SMark Brown const char *desc; 2828acb4524SMark Brown 2837769f1abSMark Brown switch (class) { 2847769f1abSMark Brown case TEST_CLASS_DEFAULT: 2857769f1abSMark Brown test_class_name = "default"; 2867769f1abSMark Brown break; 2877769f1abSMark Brown case TEST_CLASS_SYSTEM: 2887769f1abSMark Brown test_class_name = "system"; 2897769f1abSMark Brown break; 2907769f1abSMark Brown default: 2917769f1abSMark Brown ksft_exit_fail_msg("Unknown test class %d\n", class); 2927769f1abSMark Brown break; 2937769f1abSMark Brown } 294aba51cd0SJaroslav Kysela 29569218b59SMark Brown desc = conf_get_string(pcm_cfg, "description", NULL, NULL); 29669218b59SMark Brown if (desc) 29769218b59SMark Brown ksft_print_msg("%s.%s.%d.%d.%d.%s - %s\n", 29869218b59SMark Brown test_class_name, test_name, 29969218b59SMark Brown data->card, data->device, data->subdevice, 30069218b59SMark Brown snd_pcm_stream_name(data->stream), 30169218b59SMark Brown desc); 30269218b59SMark Brown 30369218b59SMark Brown 304aba51cd0SJaroslav Kysela snd_pcm_hw_params_alloca(&hw_params); 305aba51cd0SJaroslav Kysela snd_pcm_sw_params_alloca(&sw_params); 306aba51cd0SJaroslav Kysela 307348d09fcSJaroslav Kysela cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE"); 308aba51cd0SJaroslav Kysela format = snd_pcm_format_value(cs); 309aba51cd0SJaroslav Kysela if (format == SND_PCM_FORMAT_UNKNOWN) 310aba51cd0SJaroslav Kysela ksft_exit_fail_msg("Wrong format '%s'\n", cs); 311348d09fcSJaroslav Kysela conf_get_string_array(pcm_cfg, "alt_formats", NULL, 312348d09fcSJaroslav Kysela alt_formats, ARRAY_SIZE(alt_formats), NULL); 313348d09fcSJaroslav Kysela rate = conf_get_long(pcm_cfg, "rate", NULL, 48000); 314348d09fcSJaroslav Kysela channels = conf_get_long(pcm_cfg, "channels", NULL, 2); 315348d09fcSJaroslav Kysela period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096); 316348d09fcSJaroslav Kysela buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384); 317aba51cd0SJaroslav Kysela 318aba51cd0SJaroslav Kysela samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8); 319aba51cd0SJaroslav Kysela if (!samples) 320aba51cd0SJaroslav Kysela ksft_exit_fail_msg("Out of memory\n"); 321aba51cd0SJaroslav Kysela snd_pcm_format_set_silence(format, samples, rate * channels); 322aba51cd0SJaroslav Kysela 323aba51cd0SJaroslav Kysela sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice); 324aba51cd0SJaroslav Kysela err = snd_pcm_open(&handle, name, data->stream, 0); 325aba51cd0SJaroslav Kysela if (err < 0) { 326aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err)); 327aba51cd0SJaroslav Kysela goto __close; 328aba51cd0SJaroslav Kysela } 329aba51cd0SJaroslav Kysela 330aba51cd0SJaroslav Kysela err = snd_pcm_hw_params_any(handle, hw_params); 331aba51cd0SJaroslav Kysela if (err < 0) { 332aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err)); 333aba51cd0SJaroslav Kysela goto __close; 334aba51cd0SJaroslav Kysela } 335aba51cd0SJaroslav Kysela err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0); 336aba51cd0SJaroslav Kysela if (err < 0) { 337aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err)); 338aba51cd0SJaroslav Kysela goto __close; 339aba51cd0SJaroslav Kysela } 340aba51cd0SJaroslav Kysela err = snd_pcm_hw_params_set_access(handle, hw_params, access); 341aba51cd0SJaroslav Kysela if (err < 0) { 342aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s", 343aba51cd0SJaroslav Kysela snd_pcm_access_name(access), snd_strerror(err)); 344aba51cd0SJaroslav Kysela goto __close; 345aba51cd0SJaroslav Kysela } 346348d09fcSJaroslav Kysela i = -1; 347aba51cd0SJaroslav Kysela __format: 348aba51cd0SJaroslav Kysela err = snd_pcm_hw_params_set_format(handle, hw_params, format); 349aba51cd0SJaroslav Kysela if (err < 0) { 350348d09fcSJaroslav Kysela i++; 351348d09fcSJaroslav Kysela if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) { 352348d09fcSJaroslav Kysela old_format = format; 353348d09fcSJaroslav Kysela format = snd_pcm_format_value(alt_formats[i]); 354348d09fcSJaroslav Kysela if (format != SND_PCM_FORMAT_UNKNOWN) { 355348d09fcSJaroslav Kysela ksft_print_msg("%s.%d.%d.%d.%s.%s format %s -> %s\n", 356348d09fcSJaroslav Kysela test_name, 357aba51cd0SJaroslav Kysela data->card, data->device, data->subdevice, 358aba51cd0SJaroslav Kysela snd_pcm_stream_name(data->stream), 359348d09fcSJaroslav Kysela snd_pcm_access_name(access), 360348d09fcSJaroslav Kysela snd_pcm_format_name(old_format), 361348d09fcSJaroslav Kysela snd_pcm_format_name(format)); 362348d09fcSJaroslav Kysela samples = realloc(samples, (rate * channels * 363348d09fcSJaroslav Kysela snd_pcm_format_physical_width(format)) / 8); 364348d09fcSJaroslav Kysela if (!samples) 365348d09fcSJaroslav Kysela ksft_exit_fail_msg("Out of memory\n"); 366348d09fcSJaroslav Kysela snd_pcm_format_set_silence(format, samples, rate * channels); 367348d09fcSJaroslav Kysela goto __format; 368348d09fcSJaroslav Kysela } 369aba51cd0SJaroslav Kysela } 370aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s", 371aba51cd0SJaroslav Kysela snd_pcm_format_name(format), snd_strerror(err)); 372aba51cd0SJaroslav Kysela goto __close; 373aba51cd0SJaroslav Kysela } 374c48cafc2SMark Brown err = snd_pcm_hw_params_set_channels(handle, hw_params, channels); 375aba51cd0SJaroslav Kysela if (err < 0) { 376aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err)); 377aba51cd0SJaroslav Kysela goto __close; 378aba51cd0SJaroslav Kysela } 379aba51cd0SJaroslav Kysela rrate = rate; 380aba51cd0SJaroslav Kysela err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0); 381aba51cd0SJaroslav Kysela if (err < 0) { 382aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err)); 383aba51cd0SJaroslav Kysela goto __close; 384aba51cd0SJaroslav Kysela } 385aba51cd0SJaroslav Kysela if (rrate != rate) { 386bd574889SMirsad Goran Todorovac snprintf(msg, sizeof(msg), "rate mismatch %ld != %d", rate, rrate); 387aba51cd0SJaroslav Kysela goto __close; 388aba51cd0SJaroslav Kysela } 389aba51cd0SJaroslav Kysela rperiod_size = period_size; 390aba51cd0SJaroslav Kysela err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0); 391aba51cd0SJaroslav Kysela if (err < 0) { 392aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err)); 393aba51cd0SJaroslav Kysela goto __close; 394aba51cd0SJaroslav Kysela } 395aba51cd0SJaroslav Kysela rbuffer_size = buffer_size; 396aba51cd0SJaroslav Kysela err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size); 397aba51cd0SJaroslav Kysela if (err < 0) { 398aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err)); 399aba51cd0SJaroslav Kysela goto __close; 400aba51cd0SJaroslav Kysela } 401aba51cd0SJaroslav Kysela err = snd_pcm_hw_params(handle, hw_params); 402aba51cd0SJaroslav Kysela if (err < 0) { 403aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err)); 404aba51cd0SJaroslav Kysela goto __close; 405aba51cd0SJaroslav Kysela } 406aba51cd0SJaroslav Kysela 407aba51cd0SJaroslav Kysela err = snd_pcm_sw_params_current(handle, sw_params); 408aba51cd0SJaroslav Kysela if (err < 0) { 409aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err)); 410aba51cd0SJaroslav Kysela goto __close; 411aba51cd0SJaroslav Kysela } 412aba51cd0SJaroslav Kysela if (data->stream == SND_PCM_STREAM_PLAYBACK) { 413aba51cd0SJaroslav Kysela start_threshold = (rbuffer_size / rperiod_size) * rperiod_size; 414aba51cd0SJaroslav Kysela } else { 415aba51cd0SJaroslav Kysela start_threshold = rperiod_size; 416aba51cd0SJaroslav Kysela } 417aba51cd0SJaroslav Kysela err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold); 418aba51cd0SJaroslav Kysela if (err < 0) { 419aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err)); 420aba51cd0SJaroslav Kysela goto __close; 421aba51cd0SJaroslav Kysela } 422aba51cd0SJaroslav Kysela err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size); 423aba51cd0SJaroslav Kysela if (err < 0) { 424aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err)); 425aba51cd0SJaroslav Kysela goto __close; 426aba51cd0SJaroslav Kysela } 427aba51cd0SJaroslav Kysela err = snd_pcm_sw_params(handle, sw_params); 428aba51cd0SJaroslav Kysela if (err < 0) { 429aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err)); 430aba51cd0SJaroslav Kysela goto __close; 431aba51cd0SJaroslav Kysela } 432aba51cd0SJaroslav Kysela 4337769f1abSMark Brown ksft_print_msg("%s.%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n", 4347769f1abSMark Brown test_class_name, test_name, 435aba51cd0SJaroslav Kysela data->card, data->device, data->subdevice, 436aba51cd0SJaroslav Kysela snd_pcm_stream_name(data->stream), 437aba51cd0SJaroslav Kysela snd_pcm_access_name(access), 438aba51cd0SJaroslav Kysela snd_pcm_format_name(format), 439aba51cd0SJaroslav Kysela (long)rate, (long)channels, 440aba51cd0SJaroslav Kysela (long)rperiod_size, (long)rbuffer_size, 441aba51cd0SJaroslav Kysela (long)start_threshold); 442aba51cd0SJaroslav Kysela 44334fb956cSMark Brown /* Set all the params, actually run the test */ 44434fb956cSMark Brown skip = false; 44534fb956cSMark Brown 446aba51cd0SJaroslav Kysela timestamp_now(&tstamp); 447*0825d54aSNícolas F. R. A. Prado for (i = 0; i < duration_s; i++) { 448aba51cd0SJaroslav Kysela if (data->stream == SND_PCM_STREAM_PLAYBACK) { 449aba51cd0SJaroslav Kysela frames = snd_pcm_writei(handle, samples, rate); 450aba51cd0SJaroslav Kysela if (frames < 0) { 451aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), 452bd574889SMirsad Goran Todorovac "Write failed: expected %ld, wrote %li", rate, frames); 453aba51cd0SJaroslav Kysela goto __close; 454aba51cd0SJaroslav Kysela } 455aba51cd0SJaroslav Kysela if (frames < rate) { 456aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), 457bd574889SMirsad Goran Todorovac "expected %ld, wrote %li", rate, frames); 458aba51cd0SJaroslav Kysela goto __close; 459aba51cd0SJaroslav Kysela } 460aba51cd0SJaroslav Kysela } else { 461aba51cd0SJaroslav Kysela frames = snd_pcm_readi(handle, samples, rate); 462aba51cd0SJaroslav Kysela if (frames < 0) { 463aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), 464bd574889SMirsad Goran Todorovac "expected %ld, wrote %li", rate, frames); 465aba51cd0SJaroslav Kysela goto __close; 466aba51cd0SJaroslav Kysela } 467aba51cd0SJaroslav Kysela if (frames < rate) { 468aba51cd0SJaroslav Kysela snprintf(msg, sizeof(msg), 469bd574889SMirsad Goran Todorovac "expected %ld, wrote %li", rate, frames); 470aba51cd0SJaroslav Kysela goto __close; 471aba51cd0SJaroslav Kysela } 472aba51cd0SJaroslav Kysela } 473aba51cd0SJaroslav Kysela } 474aba51cd0SJaroslav Kysela 475aba51cd0SJaroslav Kysela snd_pcm_drain(handle); 476aba51cd0SJaroslav Kysela ms = timestamp_diff_ms(&tstamp); 477*0825d54aSNícolas F. R. A. Prado if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) { 478*0825d54aSNícolas F. R. A. Prado snprintf(msg, sizeof(msg), "time mismatch: expected %dms got %lld", duration_ms, ms); 479aba51cd0SJaroslav Kysela goto __close; 480aba51cd0SJaroslav Kysela } 481aba51cd0SJaroslav Kysela 482aba51cd0SJaroslav Kysela msg[0] = '\0'; 483aba51cd0SJaroslav Kysela pass = true; 484aba51cd0SJaroslav Kysela __close: 48569218b59SMark Brown pthread_mutex_lock(&results_lock); 48669218b59SMark Brown 48734fb956cSMark Brown switch (class) { 48834fb956cSMark Brown case TEST_CLASS_SYSTEM: 48934fb956cSMark Brown test_class_name = "system"; 49034fb956cSMark Brown /* 49134fb956cSMark Brown * Anything specified as specific to this system 49234fb956cSMark Brown * should always be supported. 49334fb956cSMark Brown */ 49434fb956cSMark Brown ksft_test_result(!skip, "%s.%s.%d.%d.%d.%s.params\n", 49534fb956cSMark Brown test_class_name, test_name, 49634fb956cSMark Brown data->card, data->device, data->subdevice, 49734fb956cSMark Brown snd_pcm_stream_name(data->stream)); 49834fb956cSMark Brown break; 49934fb956cSMark Brown default: 50034fb956cSMark Brown break; 50134fb956cSMark Brown } 5027769f1abSMark Brown 50334fb956cSMark Brown if (!skip) 50438bd221aSMark Brown ksft_test_result(pass, "%s.%s.%d.%d.%d.%s\n", 5057769f1abSMark Brown test_class_name, test_name, 506aba51cd0SJaroslav Kysela data->card, data->device, data->subdevice, 50738bd221aSMark Brown snd_pcm_stream_name(data->stream)); 50834fb956cSMark Brown else 50938bd221aSMark Brown ksft_test_result_skip("%s.%s.%d.%d.%d.%s\n", 51034fb956cSMark Brown test_class_name, test_name, 51134fb956cSMark Brown data->card, data->device, data->subdevice, 51238bd221aSMark Brown snd_pcm_stream_name(data->stream)); 51338bd221aSMark Brown 51438bd221aSMark Brown if (msg[0]) 51538bd221aSMark Brown ksft_print_msg("%s\n", msg); 51669218b59SMark Brown 51769218b59SMark Brown pthread_mutex_unlock(&results_lock); 51869218b59SMark Brown 519aba51cd0SJaroslav Kysela free(samples); 520aba51cd0SJaroslav Kysela if (handle) 521aba51cd0SJaroslav Kysela snd_pcm_close(handle); 522aba51cd0SJaroslav Kysela } 523aba51cd0SJaroslav Kysela 5247769f1abSMark Brown void run_time_tests(struct pcm_data *pcm, enum test_class class, 5257769f1abSMark Brown snd_config_t *cfg) 5267769f1abSMark Brown { 5277769f1abSMark Brown const char *test_name, *test_type; 5287769f1abSMark Brown snd_config_t *pcm_cfg; 5297769f1abSMark Brown snd_config_iterator_t i, next; 5307769f1abSMark Brown 5317769f1abSMark Brown if (!cfg) 5327769f1abSMark Brown return; 5337769f1abSMark Brown 5347769f1abSMark Brown cfg = conf_get_subtree(cfg, "test", NULL); 5357769f1abSMark Brown if (cfg == NULL) 5367769f1abSMark Brown return; 5377769f1abSMark Brown 5387769f1abSMark Brown snd_config_for_each(i, next, cfg) { 5397769f1abSMark Brown pcm_cfg = snd_config_iterator_entry(i); 5407769f1abSMark Brown if (snd_config_get_id(pcm_cfg, &test_name) < 0) 5417769f1abSMark Brown ksft_exit_fail_msg("snd_config_get_id\n"); 5427769f1abSMark Brown test_type = conf_get_string(pcm_cfg, "type", NULL, "time"); 5437769f1abSMark Brown if (strcmp(test_type, "time") == 0) 5447769f1abSMark Brown test_pcm_time(pcm, class, test_name, pcm_cfg); 5457769f1abSMark Brown else 5467769f1abSMark Brown ksft_exit_fail_msg("unknown test type '%s'\n", test_type); 5477769f1abSMark Brown } 5487769f1abSMark Brown } 5497769f1abSMark Brown 55069218b59SMark Brown void *card_thread(void *data) 55169218b59SMark Brown { 55269218b59SMark Brown struct card_data *card = data; 55369218b59SMark Brown struct pcm_data *pcm; 55469218b59SMark Brown 55569218b59SMark Brown for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) { 55669218b59SMark Brown if (pcm->card != card->card) 55769218b59SMark Brown continue; 55869218b59SMark Brown 55969218b59SMark Brown run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config); 56069218b59SMark Brown run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config); 56169218b59SMark Brown } 56269218b59SMark Brown 56369218b59SMark Brown return 0; 56469218b59SMark Brown } 56569218b59SMark Brown 566aba51cd0SJaroslav Kysela int main(void) 567aba51cd0SJaroslav Kysela { 56869218b59SMark Brown struct card_data *card; 569aba51cd0SJaroslav Kysela struct pcm_data *pcm; 57069218b59SMark Brown snd_config_t *global_config, *cfg, *pcm_cfg; 5717769f1abSMark Brown int num_pcm_tests = 0, num_tests, num_std_pcm_tests; 57269218b59SMark Brown int ret; 57369218b59SMark Brown void *thread_ret; 574aba51cd0SJaroslav Kysela 575aba51cd0SJaroslav Kysela ksft_print_header(); 576aba51cd0SJaroslav Kysela 577348d09fcSJaroslav Kysela global_config = conf_load_from_file("pcm-test.conf"); 578348d09fcSJaroslav Kysela default_pcm_config = conf_get_subtree(global_config, "pcm", NULL); 579348d09fcSJaroslav Kysela if (default_pcm_config == NULL) 580348d09fcSJaroslav Kysela ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n"); 581348d09fcSJaroslav Kysela 582aba51cd0SJaroslav Kysela conf_load(); 583aba51cd0SJaroslav Kysela 584aba51cd0SJaroslav Kysela find_pcms(); 585aba51cd0SJaroslav Kysela 5867769f1abSMark Brown num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL); 5877769f1abSMark Brown 588348d09fcSJaroslav Kysela for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) { 5897769f1abSMark Brown num_pcm_tests += num_std_pcm_tests; 590348d09fcSJaroslav Kysela cfg = pcm->pcm_config; 591348d09fcSJaroslav Kysela if (cfg == NULL) 5927769f1abSMark Brown continue; 59334fb956cSMark Brown /* Setting params is reported as a separate test */ 59434fb956cSMark Brown num_tests = conf_get_count(cfg, "test", NULL) * 2; 595348d09fcSJaroslav Kysela if (num_tests > 0) 596348d09fcSJaroslav Kysela num_pcm_tests += num_tests; 597348d09fcSJaroslav Kysela } 598348d09fcSJaroslav Kysela 599348d09fcSJaroslav Kysela ksft_set_plan(num_missing + num_pcm_tests); 600aba51cd0SJaroslav Kysela 601aba51cd0SJaroslav Kysela for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) { 602aba51cd0SJaroslav Kysela ksft_test_result(false, "test.missing.%d.%d.%d.%s\n", 603aba51cd0SJaroslav Kysela pcm->card, pcm->device, pcm->subdevice, 604aba51cd0SJaroslav Kysela snd_pcm_stream_name(pcm->stream)); 605aba51cd0SJaroslav Kysela } 606aba51cd0SJaroslav Kysela 60769218b59SMark Brown for (card = card_list; card != NULL; card = card->next) { 60869218b59SMark Brown ret = pthread_create(&card->thread, NULL, card_thread, card); 60969218b59SMark Brown if (ret != 0) { 61069218b59SMark Brown ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n", 61169218b59SMark Brown card->card, ret, 61269218b59SMark Brown strerror(errno)); 61369218b59SMark Brown } 61469218b59SMark Brown } 61569218b59SMark Brown 61669218b59SMark Brown for (card = card_list; card != NULL; card = card->next) { 61769218b59SMark Brown ret = pthread_join(card->thread, &thread_ret); 61869218b59SMark Brown if (ret != 0) { 61969218b59SMark Brown ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n", 62069218b59SMark Brown card->card, ret, 62169218b59SMark Brown strerror(errno)); 62269218b59SMark Brown } 623aba51cd0SJaroslav Kysela } 624aba51cd0SJaroslav Kysela 625348d09fcSJaroslav Kysela snd_config_delete(global_config); 626aba51cd0SJaroslav Kysela conf_free(); 627aba51cd0SJaroslav Kysela 628aba51cd0SJaroslav Kysela ksft_exit_pass(); 629aba51cd0SJaroslav Kysela 630aba51cd0SJaroslav Kysela return 0; 631aba51cd0SJaroslav Kysela } 632