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 
timestamp_now(timestamp_t * tstamp)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 
timestamp_diff_ms(timestamp_t * tstamp)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 
device_from_id(snd_config_t * node)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 
missing_device(int card,int device,int subdevice,snd_pcm_stream_t stream)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 
missing_devices(int card,snd_config_t * card_config)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 
find_pcms(void)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 
test_pcm_time(struct pcm_data * data,enum test_class class,const char * test_name,snd_config_t * pcm_cfg)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 {
260*61ba93b4SDing Xiang 	char name[64], msg[256];
2617d43f51eSNícolas F. R. A. Prado 	const int duration_s = 2, margin_ms = 100;
2620825d54aSNí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);
4470825d54aSNí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);
4770825d54aSNícolas F. R. A. Prado 	if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) {
4780825d54aSNí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 
run_time_tests(struct pcm_data * pcm,enum test_class class,snd_config_t * cfg)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 
card_thread(void * data)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 
main(void)566aba51cd0SJaroslav Kysela int main(void)
567aba51cd0SJaroslav Kysela {
56869218b59SMark Brown 	struct card_data *card;
569aba51cd0SJaroslav Kysela 	struct pcm_data *pcm;
570*61ba93b4SDing Xiang 	snd_config_t *global_config, *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