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