Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/alsa/pcm-test.c
26285 views
1
// SPDX-License-Identifier: GPL-2.0
2
//
3
// kselftest for the ALSA PCM API
4
//
5
// Original author: Jaroslav Kysela <[email protected]>
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
244
ksft_print_msg("%s.0 - %s\n", card_data->name,
245
snd_pcm_info_get_id(pcm_info));
246
247
count = snd_pcm_info_get_subdevices_count(pcm_info);
248
for (subdev = 0; subdev < count; subdev++) {
249
sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream));
250
if (conf_get_bool(card_config, key, "skip", false)) {
251
ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev,
252
subdev, snd_pcm_stream_name(stream));
253
continue;
254
}
255
pcm_data = calloc(1, sizeof(*pcm_data));
256
if (!pcm_data)
257
ksft_exit_fail_msg("Out of memory\n");
258
pcm_data->card = card;
259
pcm_data->device = dev;
260
pcm_data->subdevice = subdev;
261
pcm_data->card_name = card_data->name;
262
pcm_data->stream = stream;
263
pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL);
264
pcm_data->next = pcm_list;
265
pcm_list = pcm_data;
266
}
267
}
268
}
269
270
/* check for missing devices */
271
missing_devices(card, card_config);
272
273
next_card:
274
snd_ctl_close(handle);
275
if (snd_card_next(&card) < 0) {
276
ksft_print_msg("snd_card_next");
277
break;
278
}
279
}
280
281
snd_config_delete(config);
282
}
283
284
static void test_pcm_time(struct pcm_data *data, enum test_class class,
285
const char *test_name, snd_config_t *pcm_cfg)
286
{
287
char name[64], msg[256];
288
const int duration_s = 2, margin_ms = 100;
289
const int duration_ms = duration_s * 1000;
290
const char *cs;
291
int i, err;
292
snd_pcm_t *handle = NULL;
293
snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
294
snd_pcm_format_t format, old_format;
295
const char *alt_formats[8];
296
unsigned char *samples = NULL;
297
snd_pcm_sframes_t frames;
298
long long ms;
299
long rate, channels, period_size, buffer_size;
300
unsigned int rrate;
301
snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold;
302
timestamp_t tstamp;
303
bool pass = false;
304
snd_pcm_hw_params_t *hw_params;
305
snd_pcm_sw_params_t *sw_params;
306
const char *test_class_name;
307
bool skip = true;
308
const char *desc;
309
310
switch (class) {
311
case TEST_CLASS_DEFAULT:
312
test_class_name = "default";
313
break;
314
case TEST_CLASS_SYSTEM:
315
test_class_name = "system";
316
break;
317
default:
318
ksft_exit_fail_msg("Unknown test class %d\n", class);
319
break;
320
}
321
322
desc = conf_get_string(pcm_cfg, "description", NULL, NULL);
323
if (desc)
324
ksft_print_msg("%s.%s.%s.%d.%d.%s - %s\n",
325
test_class_name, test_name,
326
data->card_name, data->device, data->subdevice,
327
snd_pcm_stream_name(data->stream),
328
desc);
329
330
331
snd_pcm_hw_params_alloca(&hw_params);
332
snd_pcm_sw_params_alloca(&sw_params);
333
334
cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE");
335
format = snd_pcm_format_value(cs);
336
if (format == SND_PCM_FORMAT_UNKNOWN)
337
ksft_exit_fail_msg("Wrong format '%s'\n", cs);
338
conf_get_string_array(pcm_cfg, "alt_formats", NULL,
339
alt_formats, ARRAY_SIZE(alt_formats), NULL);
340
rate = conf_get_long(pcm_cfg, "rate", NULL, 48000);
341
channels = conf_get_long(pcm_cfg, "channels", NULL, 2);
342
period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096);
343
buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384);
344
345
samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8);
346
if (!samples)
347
ksft_exit_fail_msg("Out of memory\n");
348
snd_pcm_format_set_silence(format, samples, rate * channels);
349
350
sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice);
351
err = snd_pcm_open(&handle, name, data->stream, 0);
352
if (err < 0) {
353
snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err));
354
goto __close;
355
}
356
357
err = snd_pcm_hw_params_any(handle, hw_params);
358
if (err < 0) {
359
snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err));
360
goto __close;
361
}
362
err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
363
if (err < 0) {
364
snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err));
365
goto __close;
366
}
367
err = snd_pcm_hw_params_set_access(handle, hw_params, access);
368
if (err < 0) {
369
snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s",
370
snd_pcm_access_name(access), snd_strerror(err));
371
goto __close;
372
}
373
i = -1;
374
__format:
375
err = snd_pcm_hw_params_set_format(handle, hw_params, format);
376
if (err < 0) {
377
i++;
378
if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) {
379
old_format = format;
380
format = snd_pcm_format_value(alt_formats[i]);
381
if (format != SND_PCM_FORMAT_UNKNOWN) {
382
ksft_print_msg("%s.%s.%d.%d.%s.%s format %s -> %s\n",
383
test_name,
384
data->card_name, data->device, data->subdevice,
385
snd_pcm_stream_name(data->stream),
386
snd_pcm_access_name(access),
387
snd_pcm_format_name(old_format),
388
snd_pcm_format_name(format));
389
samples = realloc(samples, (rate * channels *
390
snd_pcm_format_physical_width(format)) / 8);
391
if (!samples)
392
ksft_exit_fail_msg("Out of memory\n");
393
snd_pcm_format_set_silence(format, samples, rate * channels);
394
goto __format;
395
}
396
}
397
snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s",
398
snd_pcm_format_name(format), snd_strerror(err));
399
goto __close;
400
}
401
err = snd_pcm_hw_params_set_channels(handle, hw_params, channels);
402
if (err < 0) {
403
snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err));
404
goto __close;
405
}
406
rrate = rate;
407
err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
408
if (err < 0) {
409
snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err));
410
goto __close;
411
}
412
if (rrate != rate) {
413
snprintf(msg, sizeof(msg), "rate mismatch %ld != %u", rate, rrate);
414
goto __close;
415
}
416
rperiod_size = period_size;
417
err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0);
418
if (err < 0) {
419
snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err));
420
goto __close;
421
}
422
rbuffer_size = buffer_size;
423
err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size);
424
if (err < 0) {
425
snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err));
426
goto __close;
427
}
428
err = snd_pcm_hw_params(handle, hw_params);
429
if (err < 0) {
430
snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err));
431
goto __close;
432
}
433
434
err = snd_pcm_sw_params_current(handle, sw_params);
435
if (err < 0) {
436
snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err));
437
goto __close;
438
}
439
if (data->stream == SND_PCM_STREAM_PLAYBACK) {
440
start_threshold = (rbuffer_size / rperiod_size) * rperiod_size;
441
} else {
442
start_threshold = rperiod_size;
443
}
444
err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold);
445
if (err < 0) {
446
snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err));
447
goto __close;
448
}
449
err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size);
450
if (err < 0) {
451
snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err));
452
goto __close;
453
}
454
err = snd_pcm_sw_params(handle, sw_params);
455
if (err < 0) {
456
snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err));
457
goto __close;
458
}
459
460
ksft_print_msg("%s.%s.%s.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",
461
test_class_name, test_name,
462
data->card_name, data->device, data->subdevice,
463
snd_pcm_stream_name(data->stream),
464
snd_pcm_access_name(access),
465
snd_pcm_format_name(format),
466
(long)rate, (long)channels,
467
(long)rperiod_size, (long)rbuffer_size,
468
(long)start_threshold);
469
470
/* Set all the params, actually run the test */
471
skip = false;
472
473
timestamp_now(&tstamp);
474
for (i = 0; i < duration_s; i++) {
475
if (data->stream == SND_PCM_STREAM_PLAYBACK) {
476
frames = snd_pcm_writei(handle, samples, rate);
477
if (frames < 0) {
478
snprintf(msg, sizeof(msg),
479
"Write failed: expected %ld, wrote %li", rate, frames);
480
goto __close;
481
}
482
if (frames < rate) {
483
snprintf(msg, sizeof(msg),
484
"expected %ld, wrote %li", rate, frames);
485
goto __close;
486
}
487
} else {
488
frames = snd_pcm_readi(handle, samples, rate);
489
if (frames < 0) {
490
snprintf(msg, sizeof(msg),
491
"expected %ld, wrote %li", rate, frames);
492
goto __close;
493
}
494
if (frames < rate) {
495
snprintf(msg, sizeof(msg),
496
"expected %ld, wrote %li", rate, frames);
497
goto __close;
498
}
499
}
500
}
501
502
snd_pcm_drain(handle);
503
ms = timestamp_diff_ms(&tstamp);
504
if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) {
505
snprintf(msg, sizeof(msg), "time mismatch: expected %dms got %lld", duration_ms, ms);
506
goto __close;
507
}
508
509
msg[0] = '\0';
510
pass = true;
511
__close:
512
pthread_mutex_lock(&results_lock);
513
514
switch (class) {
515
case TEST_CLASS_SYSTEM:
516
test_class_name = "system";
517
/*
518
* Anything specified as specific to this system
519
* should always be supported.
520
*/
521
ksft_test_result(!skip, "%s.%s.%s.%d.%d.%s.params\n",
522
test_class_name, test_name,
523
data->card_name, data->device,
524
data->subdevice,
525
snd_pcm_stream_name(data->stream));
526
break;
527
default:
528
break;
529
}
530
531
if (!skip)
532
ksft_test_result(pass, "%s.%s.%s.%d.%d.%s\n",
533
test_class_name, test_name,
534
data->card_name, data->device,
535
data->subdevice,
536
snd_pcm_stream_name(data->stream));
537
else
538
ksft_test_result_skip("%s.%s.%s.%d.%d.%s\n",
539
test_class_name, test_name,
540
data->card_name, data->device,
541
data->subdevice,
542
snd_pcm_stream_name(data->stream));
543
544
if (msg[0])
545
ksft_print_msg("%s\n", msg);
546
547
pthread_mutex_unlock(&results_lock);
548
549
free(samples);
550
if (handle)
551
snd_pcm_close(handle);
552
}
553
554
void run_time_tests(struct pcm_data *pcm, enum test_class class,
555
snd_config_t *cfg)
556
{
557
const char *test_name, *test_type;
558
snd_config_t *pcm_cfg;
559
snd_config_iterator_t i, next;
560
561
if (!cfg)
562
return;
563
564
cfg = conf_get_subtree(cfg, "test", NULL);
565
if (cfg == NULL)
566
return;
567
568
snd_config_for_each(i, next, cfg) {
569
pcm_cfg = snd_config_iterator_entry(i);
570
if (snd_config_get_id(pcm_cfg, &test_name) < 0)
571
ksft_exit_fail_msg("snd_config_get_id\n");
572
test_type = conf_get_string(pcm_cfg, "type", NULL, "time");
573
if (strcmp(test_type, "time") == 0)
574
test_pcm_time(pcm, class, test_name, pcm_cfg);
575
else
576
ksft_exit_fail_msg("unknown test type '%s'\n", test_type);
577
}
578
}
579
580
void *card_thread(void *data)
581
{
582
struct card_data *card = data;
583
struct pcm_data *pcm;
584
585
for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
586
if (pcm->card != card->card)
587
continue;
588
589
run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config);
590
run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config);
591
}
592
593
return 0;
594
}
595
596
int main(void)
597
{
598
struct card_data *card;
599
struct card_cfg_data *conf;
600
struct pcm_data *pcm;
601
snd_config_t *global_config, *cfg;
602
int num_pcm_tests = 0, num_tests, num_std_pcm_tests;
603
int ret;
604
void *thread_ret;
605
606
ksft_print_header();
607
608
global_config = conf_load_from_file("pcm-test.conf");
609
default_pcm_config = conf_get_subtree(global_config, "pcm", NULL);
610
if (default_pcm_config == NULL)
611
ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n");
612
613
conf_load();
614
615
find_pcms();
616
617
for (conf = conf_cards; conf; conf = conf->next)
618
if (conf->card < 0)
619
num_missing++;
620
621
num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL);
622
623
for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
624
num_pcm_tests += num_std_pcm_tests;
625
cfg = pcm->pcm_config;
626
if (cfg == NULL)
627
continue;
628
/* Setting params is reported as a separate test */
629
num_tests = conf_get_count(cfg, "test", NULL) * 2;
630
if (num_tests > 0)
631
num_pcm_tests += num_tests;
632
}
633
634
ksft_set_plan(num_missing + num_pcm_tests);
635
636
for (conf = conf_cards; conf; conf = conf->next)
637
if (conf->card < 0)
638
ksft_test_result_fail("test.missing.%s.%s\n",
639
conf->filename, conf->config_id);
640
641
for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {
642
ksft_test_result(false, "test.missing.%s.%d.%d.%s\n",
643
pcm->card_name, pcm->device, pcm->subdevice,
644
snd_pcm_stream_name(pcm->stream));
645
}
646
647
for (card = card_list; card != NULL; card = card->next) {
648
ret = pthread_create(&card->thread, NULL, card_thread, card);
649
if (ret != 0) {
650
ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n",
651
card->card, ret,
652
strerror(errno));
653
}
654
}
655
656
for (card = card_list; card != NULL; card = card->next) {
657
ret = pthread_join(card->thread, &thread_ret);
658
if (ret != 0) {
659
ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n",
660
card->card, ret,
661
strerror(errno));
662
}
663
}
664
665
snd_config_delete(global_config);
666
conf_free();
667
668
ksft_exit_pass();
669
670
return 0;
671
}
672
673