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(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(tstamp: &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(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(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(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(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(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(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(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(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], 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(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(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(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(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(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: &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: &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(msg: "%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(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 | |
524 | void 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 | |
550 | void *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 | |
566 | int main(void) |
567 | { |
568 | struct card_data *card; |
569 | struct card_cfg_data *conf; |
570 | struct pcm_data *pcm; |
571 | snd_config_t *global_config, *cfg; |
572 | int num_pcm_tests = 0, num_tests, num_std_pcm_tests; |
573 | int ret; |
574 | void *thread_ret; |
575 | |
576 | ksft_print_header(); |
577 | |
578 | global_config = conf_load_from_file("pcm-test.conf" ); |
579 | default_pcm_config = conf_get_subtree(global_config, "pcm" , NULL); |
580 | if (default_pcm_config == NULL) |
581 | ksft_exit_fail_msg(msg: "default pcm test configuration (pcm compound) is missing\n" ); |
582 | |
583 | conf_load(); |
584 | |
585 | find_pcms(); |
586 | |
587 | for (conf = conf_cards; conf; conf = conf->next) |
588 | if (conf->card < 0) |
589 | num_missing++; |
590 | |
591 | num_std_pcm_tests = conf_get_count(default_pcm_config, "test" , NULL); |
592 | |
593 | for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) { |
594 | num_pcm_tests += num_std_pcm_tests; |
595 | cfg = pcm->pcm_config; |
596 | if (cfg == NULL) |
597 | continue; |
598 | /* Setting params is reported as a separate test */ |
599 | num_tests = conf_get_count(cfg, "test" , NULL) * 2; |
600 | if (num_tests > 0) |
601 | num_pcm_tests += num_tests; |
602 | } |
603 | |
604 | ksft_set_plan(plan: num_missing + num_pcm_tests); |
605 | |
606 | for (conf = conf_cards; conf; conf = conf->next) |
607 | if (conf->card < 0) |
608 | ksft_test_result_fail(msg: "test.missing.%s.%s\n" , |
609 | conf->filename, conf->config_id); |
610 | |
611 | for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) { |
612 | ksft_test_result(false, "test.missing.%d.%d.%d.%s\n" , |
613 | pcm->card, pcm->device, pcm->subdevice, |
614 | snd_pcm_stream_name(pcm->stream)); |
615 | } |
616 | |
617 | for (card = card_list; card != NULL; card = card->next) { |
618 | ret = pthread_create(&card->thread, NULL, card_thread, card); |
619 | if (ret != 0) { |
620 | ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n" , |
621 | card->card, ret, |
622 | strerror(errno)); |
623 | } |
624 | } |
625 | |
626 | for (card = card_list; card != NULL; card = card->next) { |
627 | ret = pthread_join(card->thread, &thread_ret); |
628 | if (ret != 0) { |
629 | ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n" , |
630 | card->card, ret, |
631 | strerror(errno)); |
632 | } |
633 | } |
634 | |
635 | snd_config_delete(global_config); |
636 | conf_free(); |
637 | |
638 | ksft_exit_pass(); |
639 | |
640 | return 0; |
641 | } |
642 | |