brintos

brintos / linux-shallow public Read only

0
0
Text · 18.4 KiB · dbd7c22 Raw
672 lines · c
1// SPDX-License-Identifier: GPL-2.02//3// kselftest for the ALSA PCM API4//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, exercising9// every PCM device it can find.  This may conflict with other system10// software if there is audio activity so is best run on a system with a11// 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 system519		 * 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	else538		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		else576			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}672