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 
22 typedef struct timespec timestamp_t;
23 
24 struct 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 
34 int num_pcms = 0;
35 struct pcm_data *pcm_list = NULL;
36 
37 int num_missing = 0;
38 struct pcm_data *pcm_missing = NULL;
39 
40 struct 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 
49 void 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 
55 long 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 
69 static 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 
84 static 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 
111 static 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 
140 static 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 
231 static 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 
448 static 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 
459 int 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 }
490