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