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 	pthread_t thread;
28 	struct card_data *next;
29 };
30 
31 struct card_data *card_list = NULL;
32 
33 struct 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 
43 struct pcm_data *pcm_list = NULL;
44 
45 int num_missing = 0;
46 struct pcm_data *pcm_missing = NULL;
47 
48 snd_config_t *default_pcm_config;
49 
50 /* Lock while reporting results since kselftest doesn't */
51 pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER;
52 
53 enum test_class {
54 	TEST_CLASS_DEFAULT,
55 	TEST_CLASS_SYSTEM,
56 };
57 
58 void 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 
64 long 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 
78 static 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 
93 static 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 
120 static 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 
149 static 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 
257 static 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], key[128], msg[256];
261 	const char *cs;
262 	int i, err;
263 	snd_pcm_t *handle = NULL;
264 	snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
265 	snd_pcm_format_t format, old_format;
266 	const char *alt_formats[8];
267 	unsigned char *samples = NULL;
268 	snd_pcm_sframes_t frames;
269 	long long ms;
270 	long rate, channels, period_size, buffer_size;
271 	unsigned int rrate;
272 	snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold;
273 	timestamp_t tstamp;
274 	bool pass = false;
275 	snd_pcm_hw_params_t *hw_params;
276 	snd_pcm_sw_params_t *sw_params;
277 	const char *test_class_name;
278 	bool skip = true;
279 	const char *desc;
280 
281 	switch (class) {
282 	case TEST_CLASS_DEFAULT:
283 		test_class_name = "default";
284 		break;
285 	case TEST_CLASS_SYSTEM:
286 		test_class_name = "system";
287 		break;
288 	default:
289 		ksft_exit_fail_msg("Unknown test class %d\n", class);
290 		break;
291 	}
292 
293 	desc = conf_get_string(pcm_cfg, "description", NULL, NULL);
294 	if (desc)
295 		ksft_print_msg("%s.%s.%d.%d.%d.%s - %s\n",
296 			       test_class_name, test_name,
297 			       data->card, data->device, data->subdevice,
298 			       snd_pcm_stream_name(data->stream),
299 			       desc);
300 
301 
302 	snd_pcm_hw_params_alloca(&hw_params);
303 	snd_pcm_sw_params_alloca(&sw_params);
304 
305 	cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE");
306 	format = snd_pcm_format_value(cs);
307 	if (format == SND_PCM_FORMAT_UNKNOWN)
308 		ksft_exit_fail_msg("Wrong format '%s'\n", cs);
309 	conf_get_string_array(pcm_cfg, "alt_formats", NULL,
310 				alt_formats, ARRAY_SIZE(alt_formats), NULL);
311 	rate = conf_get_long(pcm_cfg, "rate", NULL, 48000);
312 	channels = conf_get_long(pcm_cfg, "channels", NULL, 2);
313 	period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096);
314 	buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384);
315 
316 	samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8);
317 	if (!samples)
318 		ksft_exit_fail_msg("Out of memory\n");
319 	snd_pcm_format_set_silence(format, samples, rate * channels);
320 
321 	sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice);
322 	err = snd_pcm_open(&handle, name, data->stream, 0);
323 	if (err < 0) {
324 		snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err));
325 		goto __close;
326 	}
327 
328 	err = snd_pcm_hw_params_any(handle, hw_params);
329 	if (err < 0) {
330 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err));
331 		goto __close;
332 	}
333 	err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
334 	if (err < 0) {
335 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err));
336 		goto __close;
337 	}
338 	err = snd_pcm_hw_params_set_access(handle, hw_params, access);
339 	if (err < 0) {
340 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s",
341 					   snd_pcm_access_name(access), snd_strerror(err));
342 		goto __close;
343 	}
344 	i = -1;
345 __format:
346 	err = snd_pcm_hw_params_set_format(handle, hw_params, format);
347 	if (err < 0) {
348 		i++;
349 		if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) {
350 			old_format = format;
351 			format = snd_pcm_format_value(alt_formats[i]);
352 			if (format != SND_PCM_FORMAT_UNKNOWN) {
353 				ksft_print_msg("%s.%d.%d.%d.%s.%s format %s -> %s\n",
354 						 test_name,
355 						 data->card, data->device, data->subdevice,
356 						 snd_pcm_stream_name(data->stream),
357 						 snd_pcm_access_name(access),
358 						 snd_pcm_format_name(old_format),
359 						 snd_pcm_format_name(format));
360 				samples = realloc(samples, (rate * channels *
361 							    snd_pcm_format_physical_width(format)) / 8);
362 				if (!samples)
363 					ksft_exit_fail_msg("Out of memory\n");
364 				snd_pcm_format_set_silence(format, samples, rate * channels);
365 				goto __format;
366 			}
367 		}
368 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s",
369 					   snd_pcm_format_name(format), snd_strerror(err));
370 		goto __close;
371 	}
372 	err = snd_pcm_hw_params_set_channels(handle, hw_params, channels);
373 	if (err < 0) {
374 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err));
375 		goto __close;
376 	}
377 	rrate = rate;
378 	err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
379 	if (err < 0) {
380 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err));
381 		goto __close;
382 	}
383 	if (rrate != rate) {
384 		snprintf(msg, sizeof(msg), "rate mismatch %ld != %d", rate, rrate);
385 		goto __close;
386 	}
387 	rperiod_size = period_size;
388 	err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0);
389 	if (err < 0) {
390 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err));
391 		goto __close;
392 	}
393 	rbuffer_size = buffer_size;
394 	err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size);
395 	if (err < 0) {
396 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err));
397 		goto __close;
398 	}
399 	err = snd_pcm_hw_params(handle, hw_params);
400 	if (err < 0) {
401 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err));
402 		goto __close;
403 	}
404 
405 	err = snd_pcm_sw_params_current(handle, sw_params);
406 	if (err < 0) {
407 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err));
408 		goto __close;
409 	}
410 	if (data->stream == SND_PCM_STREAM_PLAYBACK) {
411 		start_threshold = (rbuffer_size / rperiod_size) * rperiod_size;
412 	} else {
413 		start_threshold = rperiod_size;
414 	}
415 	err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold);
416 	if (err < 0) {
417 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err));
418 		goto __close;
419 	}
420 	err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size);
421 	if (err < 0) {
422 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err));
423 		goto __close;
424 	}
425 	err = snd_pcm_sw_params(handle, sw_params);
426 	if (err < 0) {
427 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err));
428 		goto __close;
429 	}
430 
431 	ksft_print_msg("%s.%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",
432 		         test_class_name, test_name,
433 			 data->card, data->device, data->subdevice,
434 			 snd_pcm_stream_name(data->stream),
435 			 snd_pcm_access_name(access),
436 			 snd_pcm_format_name(format),
437 			 (long)rate, (long)channels,
438 			 (long)rperiod_size, (long)rbuffer_size,
439 			 (long)start_threshold);
440 
441 	/* Set all the params, actually run the test */
442 	skip = false;
443 
444 	timestamp_now(&tstamp);
445 	for (i = 0; i < 4; i++) {
446 		if (data->stream == SND_PCM_STREAM_PLAYBACK) {
447 			frames = snd_pcm_writei(handle, samples, rate);
448 			if (frames < 0) {
449 				snprintf(msg, sizeof(msg),
450 					 "Write failed: expected %ld, wrote %li", rate, frames);
451 				goto __close;
452 			}
453 			if (frames < rate) {
454 				snprintf(msg, sizeof(msg),
455 					 "expected %ld, wrote %li", rate, frames);
456 				goto __close;
457 			}
458 		} else {
459 			frames = snd_pcm_readi(handle, samples, rate);
460 			if (frames < 0) {
461 				snprintf(msg, sizeof(msg),
462 					 "expected %ld, wrote %li", rate, frames);
463 				goto __close;
464 			}
465 			if (frames < rate) {
466 				snprintf(msg, sizeof(msg),
467 					 "expected %ld, wrote %li", rate, frames);
468 				goto __close;
469 			}
470 		}
471 	}
472 
473 	snd_pcm_drain(handle);
474 	ms = timestamp_diff_ms(&tstamp);
475 	if (ms < 3900 || ms > 4100) {
476 		snprintf(msg, sizeof(msg), "time mismatch: expected 4000ms got %lld", ms);
477 		goto __close;
478 	}
479 
480 	msg[0] = '\0';
481 	pass = true;
482 __close:
483 	pthread_mutex_lock(&results_lock);
484 
485 	switch (class) {
486 	case TEST_CLASS_SYSTEM:
487 		test_class_name = "system";
488 		/*
489 		 * Anything specified as specific to this system
490 		 * should always be supported.
491 		 */
492 		ksft_test_result(!skip, "%s.%s.%d.%d.%d.%s.params\n",
493 				 test_class_name, test_name,
494 				 data->card, data->device, data->subdevice,
495 				 snd_pcm_stream_name(data->stream));
496 		break;
497 	default:
498 		break;
499 	}
500 
501 	if (!skip)
502 		ksft_test_result(pass, "%s.%s.%d.%d.%d.%s\n",
503 				 test_class_name, test_name,
504 				 data->card, data->device, data->subdevice,
505 				 snd_pcm_stream_name(data->stream));
506 	else
507 		ksft_test_result_skip("%s.%s.%d.%d.%d.%s\n",
508 				 test_class_name, test_name,
509 				 data->card, data->device, data->subdevice,
510 				 snd_pcm_stream_name(data->stream));
511 
512 	if (msg[0])
513 		ksft_print_msg("%s\n", msg);
514 
515 	pthread_mutex_unlock(&results_lock);
516 
517 	free(samples);
518 	if (handle)
519 		snd_pcm_close(handle);
520 }
521 
522 void run_time_tests(struct pcm_data *pcm, enum test_class class,
523 		    snd_config_t *cfg)
524 {
525 	const char *test_name, *test_type;
526 	snd_config_t *pcm_cfg;
527 	snd_config_iterator_t i, next;
528 
529 	if (!cfg)
530 		return;
531 
532 	cfg = conf_get_subtree(cfg, "test", NULL);
533 	if (cfg == NULL)
534 		return;
535 
536 	snd_config_for_each(i, next, cfg) {
537 		pcm_cfg = snd_config_iterator_entry(i);
538 		if (snd_config_get_id(pcm_cfg, &test_name) < 0)
539 			ksft_exit_fail_msg("snd_config_get_id\n");
540 		test_type = conf_get_string(pcm_cfg, "type", NULL, "time");
541 		if (strcmp(test_type, "time") == 0)
542 			test_pcm_time(pcm, class, test_name, pcm_cfg);
543 		else
544 			ksft_exit_fail_msg("unknown test type '%s'\n", test_type);
545 	}
546 }
547 
548 void *card_thread(void *data)
549 {
550 	struct card_data *card = data;
551 	struct pcm_data *pcm;
552 
553 	for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
554 		if (pcm->card != card->card)
555 			continue;
556 
557 		run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config);
558 		run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config);
559 	}
560 
561 	return 0;
562 }
563 
564 int main(void)
565 {
566 	struct card_data *card;
567 	struct pcm_data *pcm;
568 	snd_config_t *global_config, *cfg, *pcm_cfg;
569 	int num_pcm_tests = 0, num_tests, num_std_pcm_tests;
570 	int ret;
571 	void *thread_ret;
572 
573 	ksft_print_header();
574 
575 	global_config = conf_load_from_file("pcm-test.conf");
576 	default_pcm_config = conf_get_subtree(global_config, "pcm", NULL);
577 	if (default_pcm_config == NULL)
578 		ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n");
579 
580 	conf_load();
581 
582 	find_pcms();
583 
584 	num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL);
585 
586 	for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
587 		num_pcm_tests += num_std_pcm_tests;
588 		cfg = pcm->pcm_config;
589 		if (cfg == NULL)
590 			continue;
591 		/* Setting params is reported as a separate test */
592 		num_tests = conf_get_count(cfg, "test", NULL) * 2;
593 		if (num_tests > 0)
594 			num_pcm_tests += num_tests;
595 	}
596 
597 	ksft_set_plan(num_missing + num_pcm_tests);
598 
599 	for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {
600 		ksft_test_result(false, "test.missing.%d.%d.%d.%s\n",
601 				 pcm->card, pcm->device, pcm->subdevice,
602 				 snd_pcm_stream_name(pcm->stream));
603 	}
604 
605 	for (card = card_list; card != NULL; card = card->next) {
606 		ret = pthread_create(&card->thread, NULL, card_thread, card);
607 		if (ret != 0) {
608 			ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n",
609 					   card->card, ret,
610 					   strerror(errno));
611 		}
612 	}
613 
614 	for (card = card_list; card != NULL; card = card->next) {
615 		ret = pthread_join(card->thread, &thread_ret);
616 		if (ret != 0) {
617 			ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n",
618 					   card->card, ret,
619 					   strerror(errno));
620 		}
621 	}
622 
623 	snd_config_delete(global_config);
624 	conf_free();
625 
626 	ksft_exit_pass();
627 
628 	return 0;
629 }
630