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(msg: "clock_get_time\n");
62}
63
64long 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
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(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(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(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
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(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
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 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

source code of linux/tools/testing/selftests/alsa/pcm-test.c