Loading...
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
19#include "../kselftest.h"
20#include "alsa-local.h"
21
22typedef struct timespec timestamp_t;
23
24struct pcm_data {
25 snd_pcm_t *handle;
26 int card;
27 int device;
28 int subdevice;
29 snd_pcm_stream_t stream;
30 snd_config_t *pcm_config;
31 struct pcm_data *next;
32};
33
34int num_pcms = 0;
35struct pcm_data *pcm_list = NULL;
36
37int num_missing = 0;
38struct pcm_data *pcm_missing = NULL;
39
40struct time_test_def {
41 const char *cfg_prefix;
42 const char *format;
43 long rate;
44 long channels;
45 long period_size;
46 long buffer_size;
47};
48
49void timestamp_now(timestamp_t *tstamp)
50{
51 if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp))
52 ksft_exit_fail_msg("clock_get_time\n");
53}
54
55long long timestamp_diff_ms(timestamp_t *tstamp)
56{
57 timestamp_t now, diff;
58 timestamp_now(&now);
59 if (tstamp->tv_nsec > now.tv_nsec) {
60 diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1;
61 diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec;
62 } else {
63 diff.tv_sec = now.tv_sec - tstamp->tv_sec;
64 diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec;
65 }
66 return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L);
67}
68
69static long device_from_id(snd_config_t *node)
70{
71 const char *id;
72 char *end;
73 long v;
74
75 if (snd_config_get_id(node, &id))
76 ksft_exit_fail_msg("snd_config_get_id\n");
77 errno = 0;
78 v = strtol(id, &end, 10);
79 if (errno || *end)
80 return -1;
81 return v;
82}
83
84static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream)
85{
86 struct pcm_data *pcm_data;
87
88 for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) {
89 if (pcm_data->card != card)
90 continue;
91 if (pcm_data->device != device)
92 continue;
93 if (pcm_data->subdevice != subdevice)
94 continue;
95 if (pcm_data->stream != stream)
96 continue;
97 return;
98 }
99 pcm_data = calloc(1, sizeof(*pcm_data));
100 if (!pcm_data)
101 ksft_exit_fail_msg("Out of memory\n");
102 pcm_data->card = card;
103 pcm_data->device = device;
104 pcm_data->subdevice = subdevice;
105 pcm_data->stream = stream;
106 pcm_data->next = pcm_missing;
107 pcm_missing = pcm_data;
108 num_missing++;
109}
110
111static void missing_devices(int card, snd_config_t *card_config)
112{
113 snd_config_t *pcm_config, *node1, *node2;
114 snd_config_iterator_t i1, i2, next1, next2;
115 int device, subdevice;
116
117 pcm_config = conf_get_subtree(card_config, "pcm", NULL);
118 if (!pcm_config)
119 return;
120 snd_config_for_each(i1, next1, pcm_config) {
121 node1 = snd_config_iterator_entry(i1);
122 device = device_from_id(node1);
123 if (device < 0)
124 continue;
125 if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND)
126 continue;
127 snd_config_for_each(i2, next2, node1) {
128 node2 = snd_config_iterator_entry(i2);
129 subdevice = device_from_id(node2);
130 if (subdevice < 0)
131 continue;
132 if (conf_get_subtree(node2, "PLAYBACK", NULL))
133 missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK);
134 if (conf_get_subtree(node2, "CAPTURE", NULL))
135 missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE);
136 }
137 }
138}
139
140static void find_pcms(void)
141{
142 char name[32], key[64];
143 int card, dev, subdev, count, direction, err;
144 snd_pcm_stream_t stream;
145 struct pcm_data *pcm_data;
146 snd_ctl_t *handle;
147 snd_pcm_info_t *pcm_info;
148 snd_config_t *config, *card_config, *pcm_config;
149
150 snd_pcm_info_alloca(&pcm_info);
151
152 card = -1;
153 if (snd_card_next(&card) < 0 || card < 0)
154 return;
155
156 config = get_alsalib_config();
157
158 while (card >= 0) {
159 sprintf(name, "hw:%d", card);
160
161 err = snd_ctl_open_lconf(&handle, name, 0, config);
162 if (err < 0) {
163 ksft_print_msg("Failed to get hctl for card %d: %s\n",
164 card, snd_strerror(err));
165 goto next_card;
166 }
167
168 card_config = conf_by_card(card);
169
170 dev = -1;
171 while (1) {
172 if (snd_ctl_pcm_next_device(handle, &dev) < 0)
173 ksft_exit_fail_msg("snd_ctl_pcm_next_device\n");
174 if (dev < 0)
175 break;
176
177 for (direction = 0; direction < 2; direction++) {
178 stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
179 sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream));
180 pcm_config = conf_get_subtree(card_config, key, NULL);
181 if (conf_get_bool(card_config, key, "skip", false)) {
182 ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream));
183 continue;
184 }
185 snd_pcm_info_set_device(pcm_info, dev);
186 snd_pcm_info_set_subdevice(pcm_info, 0);
187 snd_pcm_info_set_stream(pcm_info, stream);
188 err = snd_ctl_pcm_info(handle, pcm_info);
189 if (err == -ENOENT)
190 continue;
191 if (err < 0)
192 ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n",
193 dev, 0, stream);
194 count = snd_pcm_info_get_subdevices_count(pcm_info);
195 for (subdev = 0; subdev < count; subdev++) {
196 sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream));
197 if (conf_get_bool(card_config, key, "skip", false)) {
198 ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev,
199 subdev, snd_pcm_stream_name(stream));
200 continue;
201 }
202 pcm_data = calloc(1, sizeof(*pcm_data));
203 if (!pcm_data)
204 ksft_exit_fail_msg("Out of memory\n");
205 pcm_data->card = card;
206 pcm_data->device = dev;
207 pcm_data->subdevice = subdev;
208 pcm_data->stream = stream;
209 pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL);
210 pcm_data->next = pcm_list;
211 pcm_list = pcm_data;
212 num_pcms++;
213 }
214 }
215 }
216
217 /* check for missing devices */
218 missing_devices(card, card_config);
219
220 next_card:
221 snd_ctl_close(handle);
222 if (snd_card_next(&card) < 0) {
223 ksft_print_msg("snd_card_next");
224 break;
225 }
226 }
227
228 snd_config_delete(config);
229}
230
231static void test_pcm_time1(struct pcm_data *data,
232 const struct time_test_def *test)
233{
234 char name[64], key[128], msg[256];
235 const char *cs;
236 int i, err;
237 snd_pcm_t *handle = NULL;
238 snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
239 snd_pcm_format_t format;
240 unsigned char *samples = NULL;
241 snd_pcm_sframes_t frames;
242 long long ms;
243 long rate, channels, period_size, buffer_size;
244 unsigned int rchannels;
245 unsigned int rrate;
246 snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold;
247 timestamp_t tstamp;
248 bool pass = false, automatic = true;
249 snd_pcm_hw_params_t *hw_params;
250 snd_pcm_sw_params_t *sw_params;
251 bool skip = false;
252
253 snd_pcm_hw_params_alloca(&hw_params);
254 snd_pcm_sw_params_alloca(&sw_params);
255
256 cs = conf_get_string(data->pcm_config, test->cfg_prefix, "format", test->format);
257 format = snd_pcm_format_value(cs);
258 if (format == SND_PCM_FORMAT_UNKNOWN)
259 ksft_exit_fail_msg("Wrong format '%s'\n", cs);
260 rate = conf_get_long(data->pcm_config, test->cfg_prefix, "rate", test->rate);
261 channels = conf_get_long(data->pcm_config, test->cfg_prefix, "channels", test->channels);
262 period_size = conf_get_long(data->pcm_config, test->cfg_prefix, "period_size", test->period_size);
263 buffer_size = conf_get_long(data->pcm_config, test->cfg_prefix, "buffer_size", test->buffer_size);
264
265 automatic = strcmp(test->format, snd_pcm_format_name(format)) == 0 &&
266 test->rate == rate &&
267 test->channels == channels &&
268 test->period_size == period_size &&
269 test->buffer_size == buffer_size;
270
271 samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8);
272 if (!samples)
273 ksft_exit_fail_msg("Out of memory\n");
274 snd_pcm_format_set_silence(format, samples, rate * channels);
275
276 sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice);
277 err = snd_pcm_open(&handle, name, data->stream, 0);
278 if (err < 0) {
279 snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err));
280 goto __close;
281 }
282
283 err = snd_pcm_hw_params_any(handle, hw_params);
284 if (err < 0) {
285 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err));
286 goto __close;
287 }
288 err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
289 if (err < 0) {
290 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err));
291 goto __close;
292 }
293 err = snd_pcm_hw_params_set_access(handle, hw_params, access);
294 if (err < 0) {
295 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s",
296 snd_pcm_access_name(access), snd_strerror(err));
297 goto __close;
298 }
299__format:
300 err = snd_pcm_hw_params_set_format(handle, hw_params, format);
301 if (err < 0) {
302 if (automatic && format == SND_PCM_FORMAT_S16_LE) {
303 format = SND_PCM_FORMAT_S32_LE;
304 ksft_print_msg("%s.%d.%d.%d.%s.%s format S16_LE -> S32_LE\n",
305 test->cfg_prefix,
306 data->card, data->device, data->subdevice,
307 snd_pcm_stream_name(data->stream),
308 snd_pcm_access_name(access));
309 }
310 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s",
311 snd_pcm_format_name(format), snd_strerror(err));
312 goto __close;
313 }
314 rchannels = channels;
315 err = snd_pcm_hw_params_set_channels_near(handle, hw_params, &rchannels);
316 if (err < 0) {
317 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err));
318 goto __close;
319 }
320 if (rchannels != channels) {
321 snprintf(msg, sizeof(msg), "channels unsupported %ld != %ld", channels, rchannels);
322 skip = true;
323 goto __close;
324 }
325 rrate = rate;
326 err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
327 if (err < 0) {
328 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err));
329 goto __close;
330 }
331 if (rrate != rate) {
332 snprintf(msg, sizeof(msg), "rate unsupported %ld != %ld", rate, rrate);
333 skip = true;
334 goto __close;
335 }
336 rperiod_size = period_size;
337 err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0);
338 if (err < 0) {
339 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err));
340 goto __close;
341 }
342 rbuffer_size = buffer_size;
343 err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size);
344 if (err < 0) {
345 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err));
346 goto __close;
347 }
348 err = snd_pcm_hw_params(handle, hw_params);
349 if (err < 0) {
350 snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err));
351 goto __close;
352 }
353
354 err = snd_pcm_sw_params_current(handle, sw_params);
355 if (err < 0) {
356 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err));
357 goto __close;
358 }
359 if (data->stream == SND_PCM_STREAM_PLAYBACK) {
360 start_threshold = (rbuffer_size / rperiod_size) * rperiod_size;
361 } else {
362 start_threshold = rperiod_size;
363 }
364 err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold);
365 if (err < 0) {
366 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err));
367 goto __close;
368 }
369 err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size);
370 if (err < 0) {
371 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err));
372 goto __close;
373 }
374 err = snd_pcm_sw_params(handle, sw_params);
375 if (err < 0) {
376 snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err));
377 goto __close;
378 }
379
380 ksft_print_msg("%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",
381 test->cfg_prefix,
382 data->card, data->device, data->subdevice,
383 snd_pcm_stream_name(data->stream),
384 snd_pcm_access_name(access),
385 snd_pcm_format_name(format),
386 (long)rate, (long)channels,
387 (long)rperiod_size, (long)rbuffer_size,
388 (long)start_threshold);
389
390 timestamp_now(&tstamp);
391 for (i = 0; i < 4; i++) {
392 if (data->stream == SND_PCM_STREAM_PLAYBACK) {
393 frames = snd_pcm_writei(handle, samples, rate);
394 if (frames < 0) {
395 snprintf(msg, sizeof(msg),
396 "Write failed: expected %d, wrote %li", rate, frames);
397 goto __close;
398 }
399 if (frames < rate) {
400 snprintf(msg, sizeof(msg),
401 "expected %d, wrote %li", rate, frames);
402 goto __close;
403 }
404 } else {
405 frames = snd_pcm_readi(handle, samples, rate);
406 if (frames < 0) {
407 snprintf(msg, sizeof(msg),
408 "expected %d, wrote %li", rate, frames);
409 goto __close;
410 }
411 if (frames < rate) {
412 snprintf(msg, sizeof(msg),
413 "expected %d, wrote %li", rate, frames);
414 goto __close;
415 }
416 }
417 }
418
419 snd_pcm_drain(handle);
420 ms = timestamp_diff_ms(&tstamp);
421 if (ms < 3900 || ms > 4100) {
422 snprintf(msg, sizeof(msg), "time mismatch: expected 4000ms got %lld", ms);
423 goto __close;
424 }
425
426 msg[0] = '\0';
427 pass = true;
428__close:
429 if (!skip) {
430 ksft_test_result(pass, "%s.%d.%d.%d.%s%s%s\n",
431 test->cfg_prefix,
432 data->card, data->device, data->subdevice,
433 snd_pcm_stream_name(data->stream),
434 msg[0] ? " " : "", msg);
435 } else {
436 ksft_test_result_skip("%s.%d.%d.%d.%s%s%s\n",
437 test->cfg_prefix,
438 data->card, data->device,
439 data->subdevice,
440 snd_pcm_stream_name(data->stream),
441 msg[0] ? " " : "", msg);
442 }
443 free(samples);
444 if (handle)
445 snd_pcm_close(handle);
446}
447
448static const struct time_test_def time_tests[] = {
449 /* name format rate chan period buffer */
450 { "8k.1.big", "S16_LE", 8000, 2, 8000, 32000 },
451 { "8k.2.big", "S16_LE", 8000, 2, 8000, 32000 },
452 { "44k1.2.big", "S16_LE", 44100, 2, 22050, 192000 },
453 { "48k.2.small", "S16_LE", 48000, 2, 512, 4096 },
454 { "48k.2.big", "S16_LE", 48000, 2, 24000, 192000 },
455 { "48k.6.big", "S16_LE", 48000, 6, 48000, 576000 },
456 { "96k.2.big", "S16_LE", 96000, 2, 48000, 192000 },
457};
458
459int main(void)
460{
461 struct pcm_data *pcm;
462 int i;
463
464 ksft_print_header();
465
466 conf_load();
467
468 find_pcms();
469
470 ksft_set_plan(num_missing + num_pcms * ARRAY_SIZE(time_tests));
471
472 for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {
473 ksft_test_result(false, "test.missing.%d.%d.%d.%s\n",
474 pcm->card, pcm->device, pcm->subdevice,
475 snd_pcm_stream_name(pcm->stream));
476 }
477
478 for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
479 for (i = 0; i < ARRAY_SIZE(time_tests); i++) {
480 test_pcm_time1(pcm, &time_tests[i]);
481 }
482 }
483
484 conf_free();
485
486 ksft_exit_pass();
487
488 return 0;
489}
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 snd_ctl_card_info_t *info;
28 const char *name;
29 pthread_t thread;
30 struct card_data *next;
31};
32
33struct card_data *card_list = NULL;
34
35struct 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
46struct pcm_data *pcm_list = NULL;
47
48int num_missing = 0;
49struct pcm_data *pcm_missing = NULL;
50
51snd_config_t *default_pcm_config;
52
53/* Lock while reporting results since kselftest doesn't */
54pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER;
55
56enum test_class {
57 TEST_CLASS_DEFAULT,
58 TEST_CLASS_SYSTEM,
59};
60
61void 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
67long 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
81static 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
96static 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
123static 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
152static 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
284static 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
554void 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
580void *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
596int 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}