1// SPDX-License-Identifier: GPL-2.0
2//
3// kselftest for the ALSA PCM API
4//
5// Original author: Jaroslav Kysela <perex@perex.cz>
6// Copyright (c) 2022 Red Hat Inc.
7
8// This test will iterate over all cards detected in the system, exercising
9// every PCM device it can find.  This may conflict with other system
10// software if there is audio activity so is best run on a system with a
11// minimal active userspace.
12
13#include <stdio.h>
14#include <stdlib.h>
15#include <stdbool.h>
16#include <errno.h>
17#include <assert.h>
18#include <pthread.h>
19
20#include "../kselftest.h"
21#include "alsa-local.h"
22
23typedef struct timespec timestamp_t;
24
25struct card_data {
26	int card;
27	pthread_t thread;
28	struct card_data *next;
29};
30
31struct card_data *card_list = NULL;
32
33struct pcm_data {
34	snd_pcm_t *handle;
35	int card;
36	int device;
37	int subdevice;
38	snd_pcm_stream_t stream;
39	snd_config_t *pcm_config;
40	struct pcm_data *next;
41};
42
43struct pcm_data *pcm_list = NULL;
44
45int num_missing = 0;
46struct pcm_data *pcm_missing = NULL;
47
48snd_config_t *default_pcm_config;
49
50/* Lock while reporting results since kselftest doesn't */
51pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER;
52
53enum test_class {
54	TEST_CLASS_DEFAULT,
55	TEST_CLASS_SYSTEM,
56};
57
58void timestamp_now(timestamp_t *tstamp)
59{
60	if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp))
61		ksft_exit_fail_msg("clock_get_time\n");
62}
63
64long long timestamp_diff_ms(timestamp_t *tstamp)
65{
66	timestamp_t now, diff;
67	timestamp_now(&now);
68	if (tstamp->tv_nsec > now.tv_nsec) {
69		diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1;
70		diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec;
71	} else {
72		diff.tv_sec = now.tv_sec - tstamp->tv_sec;
73		diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec;
74	}
75	return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L);
76}
77
78static long device_from_id(snd_config_t *node)
79{
80	const char *id;
81	char *end;
82	long v;
83
84	if (snd_config_get_id(node, &id))
85		ksft_exit_fail_msg("snd_config_get_id\n");
86	errno = 0;
87	v = strtol(id, &end, 10);
88	if (errno || *end)
89		return -1;
90	return v;
91}
92
93static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream)
94{
95	struct pcm_data *pcm_data;
96
97	for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) {
98		if (pcm_data->card != card)
99			continue;
100		if (pcm_data->device != device)
101			continue;
102		if (pcm_data->subdevice != subdevice)
103			continue;
104		if (pcm_data->stream != stream)
105			continue;
106		return;
107	}
108	pcm_data = calloc(1, sizeof(*pcm_data));
109	if (!pcm_data)
110		ksft_exit_fail_msg("Out of memory\n");
111	pcm_data->card = card;
112	pcm_data->device = device;
113	pcm_data->subdevice = subdevice;
114	pcm_data->stream = stream;
115	pcm_data->next = pcm_missing;
116	pcm_missing = pcm_data;
117	num_missing++;
118}
119
120static void missing_devices(int card, snd_config_t *card_config)
121{
122	snd_config_t *pcm_config, *node1, *node2;
123	snd_config_iterator_t i1, i2, next1, next2;
124	int device, subdevice;
125
126	pcm_config = conf_get_subtree(card_config, "pcm", NULL);
127	if (!pcm_config)
128		return;
129	snd_config_for_each(i1, next1, pcm_config) {
130		node1 = snd_config_iterator_entry(i1);
131		device = device_from_id(node1);
132		if (device < 0)
133			continue;
134		if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND)
135			continue;
136		snd_config_for_each(i2, next2, node1) {
137			node2 = snd_config_iterator_entry(i2);
138			subdevice = device_from_id(node2);
139			if (subdevice < 0)
140				continue;
141			if (conf_get_subtree(node2, "PLAYBACK", NULL))
142				missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK);
143			if (conf_get_subtree(node2, "CAPTURE", NULL))
144				missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE);
145		}
146	}
147}
148
149static void find_pcms(void)
150{
151	char name[32], key[64];
152	char *card_name, *card_longname;
153	int card, dev, subdev, count, direction, err;
154	snd_pcm_stream_t stream;
155	struct pcm_data *pcm_data;
156	snd_ctl_t *handle;
157	snd_pcm_info_t *pcm_info;
158	snd_config_t *config, *card_config, *pcm_config;
159	struct card_data *card_data;
160
161	snd_pcm_info_alloca(&pcm_info);
162
163	card = -1;
164	if (snd_card_next(&card) < 0 || card < 0)
165		return;
166
167	config = get_alsalib_config();
168
169	while (card >= 0) {
170		sprintf(name, "hw:%d", card);
171
172		err = snd_ctl_open_lconf(&handle, name, 0, config);
173		if (err < 0) {
174			ksft_print_msg("Failed to get hctl for card %d: %s\n",
175				       card, snd_strerror(err));
176			goto next_card;
177		}
178
179		err = snd_card_get_name(card, &card_name);
180		if (err != 0)
181			card_name = "Unknown";
182		err = snd_card_get_longname(card, &card_longname);
183		if (err != 0)
184			card_longname = "Unknown";
185		ksft_print_msg("Card %d - %s (%s)\n", card,
186			       card_name, card_longname);
187
188		card_config = conf_by_card(card);
189
190		card_data = calloc(1, sizeof(*card_data));
191		if (!card_data)
192			ksft_exit_fail_msg("Out of memory\n");
193		card_data->card = card;
194		card_data->next = card_list;
195		card_list = card_data;
196
197		dev = -1;
198		while (1) {
199			if (snd_ctl_pcm_next_device(handle, &dev) < 0)
200				ksft_exit_fail_msg("snd_ctl_pcm_next_device\n");
201			if (dev < 0)
202				break;
203
204			for (direction = 0; direction < 2; direction++) {
205				stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
206				sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream));
207				pcm_config = conf_get_subtree(card_config, key, NULL);
208				if (conf_get_bool(card_config, key, "skip", false)) {
209					ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream));
210					continue;
211				}
212				snd_pcm_info_set_device(pcm_info, dev);
213				snd_pcm_info_set_subdevice(pcm_info, 0);
214				snd_pcm_info_set_stream(pcm_info, stream);
215				err = snd_ctl_pcm_info(handle, pcm_info);
216				if (err == -ENOENT)
217					continue;
218				if (err < 0)
219					ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n",
220							   dev, 0, stream);
221				count = snd_pcm_info_get_subdevices_count(pcm_info);
222				for (subdev = 0; subdev < count; subdev++) {
223					sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream));
224					if (conf_get_bool(card_config, key, "skip", false)) {
225						ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev,
226							       subdev, snd_pcm_stream_name(stream));
227						continue;
228					}
229					pcm_data = calloc(1, sizeof(*pcm_data));
230					if (!pcm_data)
231						ksft_exit_fail_msg("Out of memory\n");
232					pcm_data->card = card;
233					pcm_data->device = dev;
234					pcm_data->subdevice = subdev;
235					pcm_data->stream = stream;
236					pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL);
237					pcm_data->next = pcm_list;
238					pcm_list = pcm_data;
239				}
240			}
241		}
242
243		/* check for missing devices */
244		missing_devices(card, card_config);
245
246	next_card:
247		snd_ctl_close(handle);
248		if (snd_card_next(&card) < 0) {
249			ksft_print_msg("snd_card_next");
250			break;
251		}
252	}
253
254	snd_config_delete(config);
255}
256
257static void test_pcm_time(struct pcm_data *data, enum test_class class,
258			  const char *test_name, snd_config_t *pcm_cfg)
259{
260	char name[64], msg[256];
261	const int duration_s = 2, margin_ms = 100;
262	const int duration_ms = duration_s * 1000;
263	const char *cs;
264	int i, err;
265	snd_pcm_t *handle = NULL;
266	snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
267	snd_pcm_format_t format, old_format;
268	const char *alt_formats[8];
269	unsigned char *samples = NULL;
270	snd_pcm_sframes_t frames;
271	long long ms;
272	long rate, channels, period_size, buffer_size;
273	unsigned int rrate;
274	snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold;
275	timestamp_t tstamp;
276	bool pass = false;
277	snd_pcm_hw_params_t *hw_params;
278	snd_pcm_sw_params_t *sw_params;
279	const char *test_class_name;
280	bool skip = true;
281	const char *desc;
282
283	switch (class) {
284	case TEST_CLASS_DEFAULT:
285		test_class_name = "default";
286		break;
287	case TEST_CLASS_SYSTEM:
288		test_class_name = "system";
289		break;
290	default:
291		ksft_exit_fail_msg("Unknown test class %d\n", class);
292		break;
293	}
294
295	desc = conf_get_string(pcm_cfg, "description", NULL, NULL);
296	if (desc)
297		ksft_print_msg("%s.%s.%d.%d.%d.%s - %s\n",
298			       test_class_name, test_name,
299			       data->card, data->device, data->subdevice,
300			       snd_pcm_stream_name(data->stream),
301			       desc);
302
303
304	snd_pcm_hw_params_alloca(&hw_params);
305	snd_pcm_sw_params_alloca(&sw_params);
306
307	cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE");
308	format = snd_pcm_format_value(cs);
309	if (format == SND_PCM_FORMAT_UNKNOWN)
310		ksft_exit_fail_msg("Wrong format '%s'\n", cs);
311	conf_get_string_array(pcm_cfg, "alt_formats", NULL,
312				alt_formats, ARRAY_SIZE(alt_formats), NULL);
313	rate = conf_get_long(pcm_cfg, "rate", NULL, 48000);
314	channels = conf_get_long(pcm_cfg, "channels", NULL, 2);
315	period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096);
316	buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384);
317
318	samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8);
319	if (!samples)
320		ksft_exit_fail_msg("Out of memory\n");
321	snd_pcm_format_set_silence(format, samples, rate * channels);
322
323	sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice);
324	err = snd_pcm_open(&handle, name, data->stream, 0);
325	if (err < 0) {
326		snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err));
327		goto __close;
328	}
329
330	err = snd_pcm_hw_params_any(handle, hw_params);
331	if (err < 0) {
332		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err));
333		goto __close;
334	}
335	err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
336	if (err < 0) {
337		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err));
338		goto __close;
339	}
340	err = snd_pcm_hw_params_set_access(handle, hw_params, access);
341	if (err < 0) {
342		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s",
343					   snd_pcm_access_name(access), snd_strerror(err));
344		goto __close;
345	}
346	i = -1;
347__format:
348	err = snd_pcm_hw_params_set_format(handle, hw_params, format);
349	if (err < 0) {
350		i++;
351		if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) {
352			old_format = format;
353			format = snd_pcm_format_value(alt_formats[i]);
354			if (format != SND_PCM_FORMAT_UNKNOWN) {
355				ksft_print_msg("%s.%d.%d.%d.%s.%s format %s -> %s\n",
356						 test_name,
357						 data->card, data->device, data->subdevice,
358						 snd_pcm_stream_name(data->stream),
359						 snd_pcm_access_name(access),
360						 snd_pcm_format_name(old_format),
361						 snd_pcm_format_name(format));
362				samples = realloc(samples, (rate * channels *
363							    snd_pcm_format_physical_width(format)) / 8);
364				if (!samples)
365					ksft_exit_fail_msg("Out of memory\n");
366				snd_pcm_format_set_silence(format, samples, rate * channels);
367				goto __format;
368			}
369		}
370		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s",
371					   snd_pcm_format_name(format), snd_strerror(err));
372		goto __close;
373	}
374	err = snd_pcm_hw_params_set_channels(handle, hw_params, channels);
375	if (err < 0) {
376		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err));
377		goto __close;
378	}
379	rrate = rate;
380	err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
381	if (err < 0) {
382		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err));
383		goto __close;
384	}
385	if (rrate != rate) {
386		snprintf(msg, sizeof(msg), "rate mismatch %ld != %d", rate, rrate);
387		goto __close;
388	}
389	rperiod_size = period_size;
390	err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0);
391	if (err < 0) {
392		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err));
393		goto __close;
394	}
395	rbuffer_size = buffer_size;
396	err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size);
397	if (err < 0) {
398		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err));
399		goto __close;
400	}
401	err = snd_pcm_hw_params(handle, hw_params);
402	if (err < 0) {
403		snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err));
404		goto __close;
405	}
406
407	err = snd_pcm_sw_params_current(handle, sw_params);
408	if (err < 0) {
409		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err));
410		goto __close;
411	}
412	if (data->stream == SND_PCM_STREAM_PLAYBACK) {
413		start_threshold = (rbuffer_size / rperiod_size) * rperiod_size;
414	} else {
415		start_threshold = rperiod_size;
416	}
417	err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold);
418	if (err < 0) {
419		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err));
420		goto __close;
421	}
422	err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size);
423	if (err < 0) {
424		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err));
425		goto __close;
426	}
427	err = snd_pcm_sw_params(handle, sw_params);
428	if (err < 0) {
429		snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err));
430		goto __close;
431	}
432
433	ksft_print_msg("%s.%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",
434		         test_class_name, test_name,
435			 data->card, data->device, data->subdevice,
436			 snd_pcm_stream_name(data->stream),
437			 snd_pcm_access_name(access),
438			 snd_pcm_format_name(format),
439			 (long)rate, (long)channels,
440			 (long)rperiod_size, (long)rbuffer_size,
441			 (long)start_threshold);
442
443	/* Set all the params, actually run the test */
444	skip = false;
445
446	timestamp_now(&tstamp);
447	for (i = 0; i < duration_s; i++) {
448		if (data->stream == SND_PCM_STREAM_PLAYBACK) {
449			frames = snd_pcm_writei(handle, samples, rate);
450			if (frames < 0) {
451				snprintf(msg, sizeof(msg),
452					 "Write failed: expected %ld, wrote %li", rate, frames);
453				goto __close;
454			}
455			if (frames < rate) {
456				snprintf(msg, sizeof(msg),
457					 "expected %ld, wrote %li", rate, frames);
458				goto __close;
459			}
460		} else {
461			frames = snd_pcm_readi(handle, samples, rate);
462			if (frames < 0) {
463				snprintf(msg, sizeof(msg),
464					 "expected %ld, wrote %li", rate, frames);
465				goto __close;
466			}
467			if (frames < rate) {
468				snprintf(msg, sizeof(msg),
469					 "expected %ld, wrote %li", rate, frames);
470				goto __close;
471			}
472		}
473	}
474
475	snd_pcm_drain(handle);
476	ms = timestamp_diff_ms(&tstamp);
477	if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) {
478		snprintf(msg, sizeof(msg), "time mismatch: expected %dms got %lld", duration_ms, ms);
479		goto __close;
480	}
481
482	msg[0] = '\0';
483	pass = true;
484__close:
485	pthread_mutex_lock(&results_lock);
486
487	switch (class) {
488	case TEST_CLASS_SYSTEM:
489		test_class_name = "system";
490		/*
491		 * Anything specified as specific to this system
492		 * should always be supported.
493		 */
494		ksft_test_result(!skip, "%s.%s.%d.%d.%d.%s.params\n",
495				 test_class_name, test_name,
496				 data->card, data->device, data->subdevice,
497				 snd_pcm_stream_name(data->stream));
498		break;
499	default:
500		break;
501	}
502
503	if (!skip)
504		ksft_test_result(pass, "%s.%s.%d.%d.%d.%s\n",
505				 test_class_name, test_name,
506				 data->card, data->device, data->subdevice,
507				 snd_pcm_stream_name(data->stream));
508	else
509		ksft_test_result_skip("%s.%s.%d.%d.%d.%s\n",
510				 test_class_name, test_name,
511				 data->card, data->device, data->subdevice,
512				 snd_pcm_stream_name(data->stream));
513
514	if (msg[0])
515		ksft_print_msg("%s\n", msg);
516
517	pthread_mutex_unlock(&results_lock);
518
519	free(samples);
520	if (handle)
521		snd_pcm_close(handle);
522}
523
524void run_time_tests(struct pcm_data *pcm, enum test_class class,
525		    snd_config_t *cfg)
526{
527	const char *test_name, *test_type;
528	snd_config_t *pcm_cfg;
529	snd_config_iterator_t i, next;
530
531	if (!cfg)
532		return;
533
534	cfg = conf_get_subtree(cfg, "test", NULL);
535	if (cfg == NULL)
536		return;
537
538	snd_config_for_each(i, next, cfg) {
539		pcm_cfg = snd_config_iterator_entry(i);
540		if (snd_config_get_id(pcm_cfg, &test_name) < 0)
541			ksft_exit_fail_msg("snd_config_get_id\n");
542		test_type = conf_get_string(pcm_cfg, "type", NULL, "time");
543		if (strcmp(test_type, "time") == 0)
544			test_pcm_time(pcm, class, test_name, pcm_cfg);
545		else
546			ksft_exit_fail_msg("unknown test type '%s'\n", test_type);
547	}
548}
549
550void *card_thread(void *data)
551{
552	struct card_data *card = data;
553	struct pcm_data *pcm;
554
555	for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
556		if (pcm->card != card->card)
557			continue;
558
559		run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config);
560		run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config);
561	}
562
563	return 0;
564}
565
566int main(void)
567{
568	struct card_data *card;
569	struct pcm_data *pcm;
570	snd_config_t *global_config, *cfg;
571	int num_pcm_tests = 0, num_tests, num_std_pcm_tests;
572	int ret;
573	void *thread_ret;
574
575	ksft_print_header();
576
577	global_config = conf_load_from_file("pcm-test.conf");
578	default_pcm_config = conf_get_subtree(global_config, "pcm", NULL);
579	if (default_pcm_config == NULL)
580		ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n");
581
582	conf_load();
583
584	find_pcms();
585
586	num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL);
587
588	for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
589		num_pcm_tests += num_std_pcm_tests;
590		cfg = pcm->pcm_config;
591		if (cfg == NULL)
592			continue;
593		/* Setting params is reported as a separate test */
594		num_tests = conf_get_count(cfg, "test", NULL) * 2;
595		if (num_tests > 0)
596			num_pcm_tests += num_tests;
597	}
598
599	ksft_set_plan(num_missing + num_pcm_tests);
600
601	for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {
602		ksft_test_result(false, "test.missing.%d.%d.%d.%s\n",
603				 pcm->card, pcm->device, pcm->subdevice,
604				 snd_pcm_stream_name(pcm->stream));
605	}
606
607	for (card = card_list; card != NULL; card = card->next) {
608		ret = pthread_create(&card->thread, NULL, card_thread, card);
609		if (ret != 0) {
610			ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n",
611					   card->card, ret,
612					   strerror(errno));
613		}
614	}
615
616	for (card = card_list; card != NULL; card = card->next) {
617		ret = pthread_join(card->thread, &thread_ret);
618		if (ret != 0) {
619			ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n",
620					   card->card, ret,
621					   strerror(errno));
622		}
623	}
624
625	snd_config_delete(global_config);
626	conf_free();
627
628	ksft_exit_pass();
629
630	return 0;
631}
632