From aba51cd0949aee50a2b93f1b32ed3668cd613649 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 8 Nov 2022 12:59:14 +0100 Subject: selftests: alsa - add PCM test This initial code does a simple sample transfer tests. By default, all PCM devices are detected and tested with short and long buffering parameters for 4 seconds. If the sample transfer timing is not in a +-100ms boundary, the test fails. Only the interleaved buffering scheme is supported in this version. The configuration may be modified with the configuration files. A specific hardware configuration is detected and activated using the sysfs regex matching. This allows to use the DMI string (/sys/class/dmi/id/* tree) or any other system parameters exposed in sysfs for the matching for the CI automation. The configuration file may also specify the PCM device list to detect the missing PCM devices. v1..v2: - added missing alsa-local.h header file Cc: Mark Brown Cc: Pierre-Louis Bossart Cc: Liam Girdwood Cc: Jesse Barnes Cc: Jimmy Cheng-Yi Chiang Cc: Curtis Malainey Cc: Brian Norris Signed-off-by: Jaroslav Kysela Reviewed-by: Mark Brown Link: https://lore.kernel.org/r/20221108115914.3751090-1-perex@perex.cz Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/Makefile | 4 +- tools/testing/selftests/alsa/alsa-local.h | 22 + tools/testing/selftests/alsa/conf.c | 379 +++++++++++++++ .../alsa/conf.d/Lenovo_ThinkPad_P1_Gen2.conf | 76 +++ tools/testing/selftests/alsa/pcm-test.c | 523 +++++++++++++++++++++ 5 files changed, 1003 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/alsa/alsa-local.h create mode 100644 tools/testing/selftests/alsa/conf.c create mode 100644 tools/testing/selftests/alsa/conf.d/Lenovo_ThinkPad_P1_Gen2.conf create mode 100644 tools/testing/selftests/alsa/pcm-test.c (limited to 'tools') diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile index fd8ddce2b1a6..8ac22d2eb2ba 100644 --- a/tools/testing/selftests/alsa/Makefile +++ b/tools/testing/selftests/alsa/Makefile @@ -7,6 +7,8 @@ ifeq ($(LDLIBS),) LDLIBS += -lasound endif -TEST_GEN_PROGS := mixer-test +TEST_GEN_PROGS := mixer-test pcm-test + +pcm-test: pcm-test.c conf.c include ../lib.mk diff --git a/tools/testing/selftests/alsa/alsa-local.h b/tools/testing/selftests/alsa/alsa-local.h new file mode 100644 index 000000000000..fd0771ac600d --- /dev/null +++ b/tools/testing/selftests/alsa/alsa-local.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// kselftest configuration helpers for the hw specific configuration +// +// Original author: Jaroslav Kysela +// Copyright (c) 2022 Red Hat Inc. + +#ifndef __ALSA_LOCAL_H +#define __ALSA_LOCAL_H + +#include + +void conf_load(void); +void conf_free(void); +snd_config_t *conf_by_card(int card); +snd_config_t *conf_get_subtree(snd_config_t *root, const char *key1, const char *key2); +int conf_get_count(snd_config_t *root, const char *key1, const char *key2); +const char *conf_get_string(snd_config_t *root, const char *key1, const char *key2, const char *def); +long conf_get_long(snd_config_t *root, const char *key1, const char *key2, long def); +int conf_get_bool(snd_config_t *root, const char *key1, const char *key2, int def); + +#endif /* __ALSA_LOCAL_H */ diff --git a/tools/testing/selftests/alsa/conf.c b/tools/testing/selftests/alsa/conf.c new file mode 100644 index 000000000000..dd6aa90deef9 --- /dev/null +++ b/tools/testing/selftests/alsa/conf.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// kselftest configuration helpers for the hw specific configuration +// +// Original author: Jaroslav Kysela +// Copyright (c) 2022 Red Hat Inc. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest.h" +#include "alsa-local.h" + +#define SYSFS_ROOT "/sys" + +struct card_data { + int card; + snd_config_t *config; + const char *filename; + struct card_data *next; +}; + +static struct card_data *conf_cards; + +static struct card_data *conf_data_by_card(int card, bool msg) +{ + struct card_data *conf; + + for (conf = conf_cards; conf; conf = conf->next) { + if (conf->card == card) { + if (msg) + ksft_print_msg("using hw card config %s for card %d\n", + conf->filename, card); + return conf; + } + } + return NULL; +} + +static int dump_config_tree(snd_config_t *top) +{ + snd_output_t *out; + int err; + + err = snd_output_stdio_attach(&out, stdout, 0); + if (err < 0) + ksft_exit_fail_msg("stdout attach\n"); + if (snd_config_save(top, out)) + ksft_exit_fail_msg("config save\n"); + snd_output_close(out); +} + +static snd_config_t *load(const char *filename) +{ + snd_config_t *dst; + snd_input_t *input; + int err; + + err = snd_input_stdio_open(&input, filename, "r"); + if (err < 0) + ksft_exit_fail_msg("Unable to parse filename %s\n", filename); + err = snd_config_top(&dst); + if (err < 0) + ksft_exit_fail_msg("Out of memory\n"); + err = snd_config_load(dst, input); + snd_input_close(input); + if (err < 0) + ksft_exit_fail_msg("Unable to parse filename %s\n", filename); + return dst; +} + +static char *sysfs_get(const char *sysfs_root, const char *id) +{ + char path[PATH_MAX], link[PATH_MAX + 1]; + struct stat sb; + ssize_t len; + char *e; + int fd; + + if (id[0] == '/') + id++; + snprintf(path, sizeof(path), "%s/%s", sysfs_root, id); + if (lstat(path, &sb) != 0) + return NULL; + if (S_ISLNK(sb.st_mode)) { + len = readlink(path, link, sizeof(link) - 1); + if (len <= 0) { + ksft_exit_fail_msg("sysfs: cannot read link '%s': %s\n", + path, strerror(errno)); + return NULL; + } + link[len] = '\0'; + e = strrchr(link, '/'); + if (e) + return strdup(e + 1); + return NULL; + } + if (S_ISDIR(sb.st_mode)) + return NULL; + if ((sb.st_mode & S_IRUSR) == 0) + return NULL; + + fd = open(path, O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + return NULL; + ksft_exit_fail_msg("sysfs: open failed for '%s': %s\n", + path, strerror(errno)); + } + len = read(fd, path, sizeof(path)-1); + close(fd); + if (len < 0) + ksft_exit_fail_msg("sysfs: unable to read value '%s': %s\n", + path, errno); + while (len > 0 && path[len-1] == '\n') + len--; + path[len] = '\0'; + e = strdup(path); + if (e == NULL) + ksft_exit_fail_msg("Out of memory\n"); + return e; +} + +static bool sysfs_match(const char *sysfs_root, snd_config_t *config) +{ + snd_config_t *node, *path_config, *regex_config; + snd_config_iterator_t i, next; + const char *path_string, *regex_string, *v; + regex_t re; + regmatch_t match[1]; + int iter = 0, ret; + + snd_config_for_each(i, next, config) { + node = snd_config_iterator_entry(i); + if (snd_config_search(node, "path", &path_config)) + ksft_exit_fail_msg("Missing path field in the sysfs block\n"); + if (snd_config_search(node, "regex", ®ex_config)) + ksft_exit_fail_msg("Missing regex field in the sysfs block\n"); + if (snd_config_get_string(path_config, &path_string)) + ksft_exit_fail_msg("Path field in the sysfs block is not a string\n"); + if (snd_config_get_string(regex_config, ®ex_string)) + ksft_exit_fail_msg("Regex field in the sysfs block is not a string\n"); + iter++; + v = sysfs_get(sysfs_root, path_string); + if (!v) + return false; + if (regcomp(&re, regex_string, REG_EXTENDED)) + ksft_exit_fail_msg("Wrong regex '%s'\n", regex_string); + ret = regexec(&re, v, 1, match, 0); + regfree(&re); + if (ret) + return false; + } + return iter > 0; +} + +static bool test_filename1(int card, const char *filename, const char *sysfs_card_root) +{ + struct card_data *data, *data2; + snd_config_t *config, *sysfs_config, *card_config, *sysfs_card_config, *node; + snd_config_iterator_t i, next; + + config = load(filename); + if (snd_config_search(config, "sysfs", &sysfs_config) || + snd_config_get_type(sysfs_config) != SND_CONFIG_TYPE_COMPOUND) + ksft_exit_fail_msg("Missing global sysfs block in filename %s\n", filename); + if (snd_config_search(config, "card", &card_config) || + snd_config_get_type(card_config) != SND_CONFIG_TYPE_COMPOUND) + ksft_exit_fail_msg("Missing global card block in filename %s\n", filename); + if (!sysfs_match(SYSFS_ROOT, sysfs_config)) + return false; + snd_config_for_each(i, next, card_config) { + node = snd_config_iterator_entry(i); + if (snd_config_search(node, "sysfs", &sysfs_card_config) || + snd_config_get_type(sysfs_card_config) != SND_CONFIG_TYPE_COMPOUND) + ksft_exit_fail_msg("Missing card sysfs block in filename %s\n", filename); + if (!sysfs_match(sysfs_card_root, sysfs_card_config)) + continue; + data = malloc(sizeof(*data)); + if (!data) + ksft_exit_fail_msg("Out of memory\n"); + data2 = conf_data_by_card(card, false); + if (data2) + ksft_exit_fail_msg("Duplicate card '%s' <-> '%s'\n", filename, data2->filename); + data->card = card; + data->filename = filename; + data->config = node; + data->next = conf_cards; + conf_cards = data; + return true; + } + return false; +} + +static bool test_filename(const char *filename) +{ + char fn[128]; + int card; + + for (card = 0; card < 32; card++) { + snprintf(fn, sizeof(fn), "%s/class/sound/card%d", SYSFS_ROOT, card); + if (access(fn, R_OK) == 0 && test_filename1(card, filename, fn)) + return true; + } + return false; +} + +static int filename_filter(const struct dirent *dirent) +{ + size_t flen; + + if (dirent == NULL) + return 0; + if (dirent->d_type == DT_DIR) + return 0; + flen = strlen(dirent->d_name); + if (flen <= 5) + return 0; + if (strncmp(&dirent->d_name[flen-5], ".conf", 5) == 0) + return 1; + return 0; +} + +void conf_load(void) +{ + const char *fn = "conf.d"; + struct dirent **namelist; + int n, j; + + n = scandir(fn, &namelist, filename_filter, alphasort); + if (n < 0) + ksft_exit_fail_msg("scandir: %s\n", strerror(errno)); + for (j = 0; j < n; j++) { + size_t sl = strlen(fn) + strlen(namelist[j]->d_name) + 2; + char *filename = malloc(sl); + if (filename == NULL) + ksft_exit_fail_msg("Out of memory\n"); + sprintf(filename, "%s/%s", fn, namelist[j]->d_name); + if (test_filename(filename)) + filename = NULL; + free(filename); + free(namelist[j]); + } + free(namelist); +} + +void conf_free(void) +{ + struct card_data *conf; + + while (conf_cards) { + conf = conf_cards; + conf_cards = conf->next; + snd_config_delete(conf->config); + } +} + +snd_config_t *conf_by_card(int card) +{ + struct card_data *conf; + + conf = conf_data_by_card(card, true); + if (conf) + return conf->config; + return NULL; +} + +static int conf_get_by_keys(snd_config_t *root, const char *key1, + const char *key2, snd_config_t **result) +{ + int ret; + + if (key1) { + ret = snd_config_search(root, key1, &root); + if (ret != -ENOENT && ret < 0) + return ret; + } + if (key2) + ret = snd_config_search(root, key2, &root); + if (ret >= 0) + *result = root; + return ret; +} + +snd_config_t *conf_get_subtree(snd_config_t *root, const char *key1, const char *key2) +{ + int ret; + + if (!root) + return NULL; + ret = conf_get_by_keys(root, key1, key2, &root); + if (ret == -ENOENT) + return NULL; + if (ret < 0) + ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret)); + return root; +} + +int conf_get_count(snd_config_t *root, const char *key1, const char *key2) +{ + snd_config_t *cfg; + snd_config_iterator_t i, next; + int count, ret; + + if (!root) + return -1; + ret = conf_get_by_keys(root, key1, key2, &cfg); + if (ret == -ENOENT) + return -1; + if (ret < 0) + ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret)); + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) + ksft_exit_fail_msg("key '%s'.'%s' is not a compound\n", key1, key2); + count = 0; + snd_config_for_each(i, next, cfg) + count++; + return count; +} + +const char *conf_get_string(snd_config_t *root, const char *key1, const char *key2, const char *def) +{ + snd_config_t *cfg; + const char *s; + int ret; + + if (!root) + return def; + ret = conf_get_by_keys(root, key1, key2, &cfg); + if (ret == -ENOENT) + return def; + if (ret < 0) + ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret)); + if (snd_config_get_string(cfg, &s)) + ksft_exit_fail_msg("key '%s'.'%s' is not a string\n", key1, key2); + return s; +} + +long conf_get_long(snd_config_t *root, const char *key1, const char *key2, long def) +{ + snd_config_t *cfg; + long l; + int ret; + + if (!root) + return def; + ret = conf_get_by_keys(root, key1, key2, &cfg); + if (ret == -ENOENT) + return def; + if (ret < 0) + ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret)); + if (snd_config_get_integer(cfg, &l)) + ksft_exit_fail_msg("key '%s'.'%s' is not an integer\n", key1, key2); + return l; +} + +int conf_get_bool(snd_config_t *root, const char *key1, const char *key2, int def) +{ + snd_config_t *cfg; + long l; + int ret; + + if (!root) + return def; + ret = conf_get_by_keys(root, key1, key2, &cfg); + if (ret == -ENOENT) + return def; + if (ret < 0) + ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret)); + ret = snd_config_get_bool(cfg); + if (ret < 0) + ksft_exit_fail_msg("key '%s'.'%s' is not an bool\n", key1, key2); + return !!ret; +} diff --git a/tools/testing/selftests/alsa/conf.d/Lenovo_ThinkPad_P1_Gen2.conf b/tools/testing/selftests/alsa/conf.d/Lenovo_ThinkPad_P1_Gen2.conf new file mode 100644 index 000000000000..0a83f35d43eb --- /dev/null +++ b/tools/testing/selftests/alsa/conf.d/Lenovo_ThinkPad_P1_Gen2.conf @@ -0,0 +1,76 @@ +# +# Example configuration for Lenovo ThinkPad P1 Gen2 +# + +# +# Use regex match for the string read from the given sysfs path +# +# The sysfs root directory (/sys) is hardwired in the test code +# (may be changed on demand). +# +# All strings must match. +# +sysfs [ + { + path "class/dmi/id/product_sku" + regex "LENOVO_MT_20QU_BU_Think_FM_ThinkPad P1 Gen 2" + } +] + +card.hda { + # + # Use regex match for the /sys/class/sound/card*/ tree (relative) + # + sysfs [ + { + path "device/subsystem_device" + regex "0x229e" + } + { + path "device/subsystem_vendor" + regex "0x17aa" + } + ] + + # + # PCM configuration + # + # pcm.0.0 - device 0 subdevice 0 + # + pcm.0.0 { + PLAYBACK { + test.time1 { + access RW_INTERLEAVED # can be omitted - default + format S16_LE # can be omitted - default + rate 48000 # can be omitted - default + channels 2 # can be omitted - default + period_size 512 + buffer_size 4096 + } + test.time2 { + access RW_INTERLEAVED + format S16_LE + rate 48000 + channels 2 + period_size 24000 + buffer_size 192000 + } + } + CAPTURE { + # use default tests, check for the presence + } + } + # + # uncomment to force the missing device checks + # + #pcm.0.2 { + # PLAYBACK { + # # check for the presence + # } + #} + #pcm.0.3 { + # CAPTURE { + # # check for the presence + # } + #} +} diff --git a/tools/testing/selftests/alsa/pcm-test.c b/tools/testing/selftests/alsa/pcm-test.c new file mode 100644 index 000000000000..2814d8f74f82 --- /dev/null +++ b/tools/testing/selftests/alsa/pcm-test.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// kselftest for the ALSA PCM API +// +// Original author: Jaroslav Kysela +// Copyright (c) 2022 Red Hat Inc. + +// This test will iterate over all cards detected in the system, exercising +// every PCM device it can find. This may conflict with other system +// software if there is audio activity so is best run on a system with a +// minimal active userspace. + +#include +#include +#include +#include +#include + +#include "../kselftest.h" +#include "alsa-local.h" + +typedef struct timespec timestamp_t; + +struct pcm_data { + snd_pcm_t *handle; + int card; + int device; + int subdevice; + snd_pcm_stream_t stream; + snd_config_t *pcm_config; + struct pcm_data *next; +}; + +static const char *alsa_config = +"ctl.hw {\n" +" @args [ CARD ]\n" +" @args.CARD.type string\n" +" type hw\n" +" card $CARD\n" +"}\n" +"pcm.hw {\n" +" @args [ CARD DEV SUBDEV ]\n" +" @args.CARD.type string\n" +" @args.DEV.type integer\n" +" @args.SUBDEV.type integer\n" +" type hw\n" +" card $CARD\n" +" device $DEV\n" +" subdevice $SUBDEV\n" +"}\n" + +; + +int num_pcms = 0; +struct pcm_data *pcm_list = NULL; + +int num_missing = 0; +struct pcm_data *pcm_missing = NULL; + +void timestamp_now(timestamp_t *tstamp) +{ + if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp)) + ksft_exit_fail_msg("clock_get_time\n"); +} + +long long timestamp_diff_ms(timestamp_t *tstamp) +{ + timestamp_t now, diff; + timestamp_now(&now); + if (tstamp->tv_nsec > now.tv_nsec) { + diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1; + diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec; + } else { + diff.tv_sec = now.tv_sec - tstamp->tv_sec; + diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec; + } + return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L); +} + +#ifdef SND_LIB_VER +#if SND_LIB_VERSION >= SND_LIB_VER(1, 2, 6) +#define LIB_HAS_LOAD_STRING +#endif +#endif + +#ifndef LIB_HAS_LOAD_STRING +static int snd_config_load_string(snd_config_t **config, const char *s, + size_t size) +{ + snd_input_t *input; + snd_config_t *dst; + int err; + + assert(config && s); + if (size == 0) + size = strlen(s); + err = snd_input_buffer_open(&input, s, size); + if (err < 0) + return err; + err = snd_config_top(&dst); + if (err < 0) { + snd_input_close(input); + return err; + } + err = snd_config_load(dst, input); + snd_input_close(input); + if (err < 0) { + snd_config_delete(dst); + return err; + } + *config = dst; + return 0; +} +#endif + +static snd_config_t *get_alsalib_config(void) +{ + snd_config_t *config; + int err; + + err = snd_config_load_string(&config, alsa_config, strlen(alsa_config)); + if (err < 0) { + ksft_print_msg("Unable to parse custom alsa-lib configuration: %s\n", + snd_strerror(err)); + ksft_exit_fail(); + } + return config; +} + +static long device_from_id(snd_config_t *node) +{ + const char *id; + char *end; + long v; + + if (snd_config_get_id(node, &id)) + ksft_exit_fail_msg("snd_config_get_id\n"); + errno = 0; + v = strtol(id, &end, 10); + if (errno || *end) + return -1; + return v; +} + +static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream) +{ + struct pcm_data *pcm_data; + + for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) { + if (pcm_data->card != card) + continue; + if (pcm_data->device != device) + continue; + if (pcm_data->subdevice != subdevice) + continue; + if (pcm_data->stream != stream) + continue; + return; + } + pcm_data = calloc(1, sizeof(*pcm_data)); + if (!pcm_data) + ksft_exit_fail_msg("Out of memory\n"); + pcm_data->card = card; + pcm_data->device = device; + pcm_data->subdevice = subdevice; + pcm_data->stream = stream; + pcm_data->next = pcm_missing; + pcm_missing = pcm_data; + num_missing++; +} + +static void missing_devices(int card, snd_config_t *card_config) +{ + snd_config_t *pcm_config, *node1, *node2; + snd_config_iterator_t i1, i2, next1, next2; + int device, subdevice; + + pcm_config = conf_get_subtree(card_config, "pcm", NULL); + if (!pcm_config) + return; + snd_config_for_each(i1, next1, pcm_config) { + node1 = snd_config_iterator_entry(i1); + device = device_from_id(node1); + if (device < 0) + continue; + if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND) + continue; + snd_config_for_each(i2, next2, node1) { + node2 = snd_config_iterator_entry(i2); + subdevice = device_from_id(node2); + if (subdevice < 0) + continue; + if (conf_get_subtree(node2, "PLAYBACK", NULL)) + missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK); + if (conf_get_subtree(node2, "CAPTURE", NULL)) + missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE); + } + } +} + +static void find_pcms(void) +{ + char name[32], key[64]; + int card, dev, subdev, count, direction, err; + snd_pcm_stream_t stream; + struct pcm_data *pcm_data; + snd_ctl_t *handle; + snd_pcm_info_t *pcm_info; + snd_config_t *config, *card_config, *pcm_config; + + snd_pcm_info_alloca(&pcm_info); + + card = -1; + if (snd_card_next(&card) < 0 || card < 0) + return; + + config = get_alsalib_config(); + + while (card >= 0) { + sprintf(name, "hw:%d", card); + + err = snd_ctl_open_lconf(&handle, name, 0, config); + if (err < 0) { + ksft_print_msg("Failed to get hctl for card %d: %s\n", + card, snd_strerror(err)); + goto next_card; + } + + card_config = conf_by_card(card); + + dev = -1; + while (1) { + if (snd_ctl_pcm_next_device(handle, &dev) < 0) + ksft_exit_fail_msg("snd_ctl_pcm_next_device\n"); + if (dev < 0) + break; + + for (direction = 0; direction < 2; direction++) { + stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK; + sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream)); + pcm_config = conf_get_subtree(card_config, key, NULL); + if (conf_get_bool(card_config, key, "skip", false)) { + ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream)); + continue; + } + snd_pcm_info_set_device(pcm_info, dev); + snd_pcm_info_set_subdevice(pcm_info, 0); + snd_pcm_info_set_stream(pcm_info, stream); + err = snd_ctl_pcm_info(handle, pcm_info); + if (err == -ENOENT) + continue; + if (err < 0) + ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n", + dev, 0, stream); + count = snd_pcm_info_get_subdevices_count(pcm_info); + for (subdev = 0; subdev < count; subdev++) { + sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream)); + if (conf_get_bool(card_config, key, "skip", false)) { + ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev, + subdev, snd_pcm_stream_name(stream)); + continue; + } + pcm_data = calloc(1, sizeof(*pcm_data)); + if (!pcm_data) + ksft_exit_fail_msg("Out of memory\n"); + pcm_data->card = card; + pcm_data->device = dev; + pcm_data->subdevice = subdev; + pcm_data->stream = stream; + pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL); + pcm_data->next = pcm_list; + pcm_list = pcm_data; + num_pcms++; + } + } + } + + /* check for missing devices */ + missing_devices(card, card_config); + + next_card: + snd_ctl_close(handle); + if (snd_card_next(&card) < 0) { + ksft_print_msg("snd_card_next"); + break; + } + } + + snd_config_delete(config); +} + +static void test_pcm_time1(struct pcm_data *data, + const char *cfg_prefix, const char *sformat, + long srate, long schannels, + long speriod_size, long sbuffer_size) +{ + char name[64], key[128], msg[256]; + const char *cs; + int i, err; + snd_pcm_t *handle = NULL; + snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED; + snd_pcm_format_t format; + unsigned char *samples = NULL; + snd_pcm_sframes_t frames; + long long ms; + long rate, channels, period_size, buffer_size; + unsigned int rrate; + snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold; + timestamp_t tstamp; + bool pass = false, automatic = true; + snd_pcm_hw_params_t *hw_params; + snd_pcm_sw_params_t *sw_params; + + snd_pcm_hw_params_alloca(&hw_params); + snd_pcm_sw_params_alloca(&sw_params); + + cs = conf_get_string(data->pcm_config, cfg_prefix, "format", sformat); + format = snd_pcm_format_value(cs); + if (format == SND_PCM_FORMAT_UNKNOWN) + ksft_exit_fail_msg("Wrong format '%s'\n", cs); + rate = conf_get_long(data->pcm_config, cfg_prefix, "rate", srate); + channels = conf_get_long(data->pcm_config, cfg_prefix, "channels", schannels); + period_size = conf_get_long(data->pcm_config, cfg_prefix, "period_size", speriod_size); + buffer_size = conf_get_long(data->pcm_config, cfg_prefix, "buffer_size", sbuffer_size); + + automatic = strcmp(sformat, snd_pcm_format_name(format)) == 0 && + srate == rate && + schannels == channels && + speriod_size == period_size && + sbuffer_size == buffer_size; + + samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8); + if (!samples) + ksft_exit_fail_msg("Out of memory\n"); + snd_pcm_format_set_silence(format, samples, rate * channels); + + sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice); + err = snd_pcm_open(&handle, name, data->stream, 0); + if (err < 0) { + snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err)); + goto __close; + } + + err = snd_pcm_hw_params_any(handle, hw_params); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err)); + goto __close; + } + err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err)); + goto __close; + } + err = snd_pcm_hw_params_set_access(handle, hw_params, access); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s", + snd_pcm_access_name(access), snd_strerror(err)); + goto __close; + } +__format: + err = snd_pcm_hw_params_set_format(handle, hw_params, format); + if (err < 0) { + if (automatic && format == SND_PCM_FORMAT_S16_LE) { + format = SND_PCM_FORMAT_S32_LE; + ksft_print_msg("%s.%d.%d.%d.%s.%s format S16_LE -> S32_LE\n", + cfg_prefix, + data->card, data->device, data->subdevice, + snd_pcm_stream_name(data->stream), + snd_pcm_access_name(access)); + } + snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s", + snd_pcm_format_name(format), snd_strerror(err)); + goto __close; + } + err = snd_pcm_hw_params_set_channels(handle, hw_params, channels); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err)); + goto __close; + } + rrate = rate; + err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err)); + goto __close; + } + if (rrate != rate) { + snprintf(msg, sizeof(msg), "rate mismatch %ld != %ld", rate, rrate); + goto __close; + } + rperiod_size = period_size; + err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err)); + goto __close; + } + rbuffer_size = buffer_size; + err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err)); + goto __close; + } + err = snd_pcm_hw_params(handle, hw_params); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err)); + goto __close; + } + + err = snd_pcm_sw_params_current(handle, sw_params); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err)); + goto __close; + } + if (data->stream == SND_PCM_STREAM_PLAYBACK) { + start_threshold = (rbuffer_size / rperiod_size) * rperiod_size; + } else { + start_threshold = rperiod_size; + } + err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err)); + goto __close; + } + err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err)); + goto __close; + } + err = snd_pcm_sw_params(handle, sw_params); + if (err < 0) { + snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err)); + goto __close; + } + + ksft_print_msg("%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n", + cfg_prefix, + data->card, data->device, data->subdevice, + snd_pcm_stream_name(data->stream), + snd_pcm_access_name(access), + snd_pcm_format_name(format), + (long)rate, (long)channels, + (long)rperiod_size, (long)rbuffer_size, + (long)start_threshold); + + timestamp_now(&tstamp); + for (i = 0; i < 4; i++) { + if (data->stream == SND_PCM_STREAM_PLAYBACK) { + frames = snd_pcm_writei(handle, samples, rate); + if (frames < 0) { + snprintf(msg, sizeof(msg), + "Write failed: expected %d, wrote %li", rate, frames); + goto __close; + } + if (frames < rate) { + snprintf(msg, sizeof(msg), + "expected %d, wrote %li", rate, frames); + goto __close; + } + } else { + frames = snd_pcm_readi(handle, samples, rate); + if (frames < 0) { + snprintf(msg, sizeof(msg), + "expected %d, wrote %li", rate, frames); + goto __close; + } + if (frames < rate) { + snprintf(msg, sizeof(msg), + "expected %d, wrote %li", rate, frames); + goto __close; + } + } + } + + snd_pcm_drain(handle); + ms = timestamp_diff_ms(&tstamp); + if (ms < 3900 || ms > 4100) { + snprintf(msg, sizeof(msg), "time mismatch: expected 4000ms got %lld", ms); + goto __close; + } + + msg[0] = '\0'; + pass = true; +__close: + ksft_test_result(pass, "%s.%d.%d.%d.%s%s%s\n", + cfg_prefix, + data->card, data->device, data->subdevice, + snd_pcm_stream_name(data->stream), + msg[0] ? " " : "", msg); + free(samples); + if (handle) + snd_pcm_close(handle); +} + +#define TESTS_PER_PCM 2 + +int main(void) +{ + struct pcm_data *pcm; + + ksft_print_header(); + + conf_load(); + + find_pcms(); + + ksft_set_plan(num_missing + num_pcms * TESTS_PER_PCM); + + for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) { + ksft_test_result(false, "test.missing.%d.%d.%d.%s\n", + pcm->card, pcm->device, pcm->subdevice, + snd_pcm_stream_name(pcm->stream)); + } + + for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) { + test_pcm_time1(pcm, "test.time1", "S16_LE", 48000, 2, 512, 4096); + test_pcm_time1(pcm, "test.time2", "S16_LE", 48000, 2, 24000, 192000); + } + + conf_free(); + + ksft_exit_pass(); + + return 0; +} -- cgit v1.2.3 From 892a0797dbbce234822f3bef9011518ffca7072e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 25 Nov 2022 15:36:54 +0000 Subject: kselftest/alsa: Add a .gitignore for the newly added PCM test The newly added PCM test produces a binary which is not ignored by git when built in tree, fix that. Fixes: aba51cd0949a ("selftests: alsa - add PCM test") Signed-off-by: Mark Brown Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20221125153654.1037868-1-broonie@kernel.org Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/.gitignore | 1 + 1 file changed, 1 insertion(+) (limited to 'tools') diff --git a/tools/testing/selftests/alsa/.gitignore b/tools/testing/selftests/alsa/.gitignore index 3bb7c41266a8..2b0d11797f25 100644 --- a/tools/testing/selftests/alsa/.gitignore +++ b/tools/testing/selftests/alsa/.gitignore @@ -1 +1,2 @@ mixer-test +pcm-test -- cgit v1.2.3 From b310092e3eec6e7be36ed2436577f406a1e5e8de Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 29 Nov 2022 09:53:06 +0100 Subject: selftests: alsa - move shared library configuration code to conf.c The minimal alsa-lib configuration code is similar in both mixer and pcm tests. Move this code to the shared conf.c source file. Also, fix the build rules inspired by rseq tests. Build libatest.so which is linked to the both test utilities dynamically. Also, set the TEST_FILES variable for lib.mk. Cc: linux-kselftest@vger.kernel.org Cc: Shuah Khan Reported-by: Mark Brown Signed-off-by: Jaroslav Kysela Tested-by: Mark Brown Link: https://lore.kernel.org/r/20221129085306.2345763-1-perex@perex.cz Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/Makefile | 13 +++++- tools/testing/selftests/alsa/alsa-local.h | 2 + tools/testing/selftests/alsa/conf.c | 69 ++++++++++++++++++++++++++++++ tools/testing/selftests/alsa/mixer-test.c | 53 +---------------------- tools/testing/selftests/alsa/pcm-test.c | 70 ------------------------------- 5 files changed, 85 insertions(+), 122 deletions(-) (limited to 'tools') diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile index 8ac22d2eb2ba..a8c0383878d3 100644 --- a/tools/testing/selftests/alsa/Makefile +++ b/tools/testing/selftests/alsa/Makefile @@ -6,9 +6,20 @@ LDLIBS += $(shell pkg-config --libs alsa) ifeq ($(LDLIBS),) LDLIBS += -lasound endif +CFLAGS += -L$(OUTPUT) -Wl,-rpath=./ + +OVERRIDE_TARGETS = 1 TEST_GEN_PROGS := mixer-test pcm-test -pcm-test: pcm-test.c conf.c +TEST_GEN_PROGS_EXTENDED := libatest.so + +TEST_FILES := conf.d include ../lib.mk + +$(OUTPUT)/libatest.so: conf.c alsa-local.h + $(CC) $(CFLAGS) -shared -fPIC $< $(LDLIBS) -o $@ + +$(OUTPUT)/%: %.c $(TEST_GEN_PROGS_EXTENDED) alsa-local.h + $(CC) $(CFLAGS) $< $(LDLIBS) -latest -o $@ diff --git a/tools/testing/selftests/alsa/alsa-local.h b/tools/testing/selftests/alsa/alsa-local.h index fd0771ac600d..65f197ea9773 100644 --- a/tools/testing/selftests/alsa/alsa-local.h +++ b/tools/testing/selftests/alsa/alsa-local.h @@ -10,6 +10,8 @@ #include +snd_config_t *get_alsalib_config(void); + void conf_load(void); void conf_free(void); snd_config_t *conf_by_card(int card); diff --git a/tools/testing/selftests/alsa/conf.c b/tools/testing/selftests/alsa/conf.c index dd6aa90deef9..c7ffc8f04195 100644 --- a/tools/testing/selftests/alsa/conf.c +++ b/tools/testing/selftests/alsa/conf.c @@ -28,6 +28,75 @@ struct card_data { static struct card_data *conf_cards; +static const char *alsa_config = +"ctl.hw {\n" +" @args [ CARD ]\n" +" @args.CARD.type string\n" +" type hw\n" +" card $CARD\n" +"}\n" +"pcm.hw {\n" +" @args [ CARD DEV SUBDEV ]\n" +" @args.CARD.type string\n" +" @args.DEV.type integer\n" +" @args.SUBDEV.type integer\n" +" type hw\n" +" card $CARD\n" +" device $DEV\n" +" subdevice $SUBDEV\n" +"}\n" +; + +#ifdef SND_LIB_VER +#if SND_LIB_VERSION >= SND_LIB_VER(1, 2, 6) +#define LIB_HAS_LOAD_STRING +#endif +#endif + +#ifndef LIB_HAS_LOAD_STRING +static int snd_config_load_string(snd_config_t **config, const char *s, + size_t size) +{ + snd_input_t *input; + snd_config_t *dst; + int err; + + assert(config && s); + if (size == 0) + size = strlen(s); + err = snd_input_buffer_open(&input, s, size); + if (err < 0) + return err; + err = snd_config_top(&dst); + if (err < 0) { + snd_input_close(input); + return err; + } + err = snd_config_load(dst, input); + snd_input_close(input); + if (err < 0) { + snd_config_delete(dst); + return err; + } + *config = dst; + return 0; +} +#endif + +snd_config_t *get_alsalib_config(void) +{ + snd_config_t *config; + int err; + + err = snd_config_load_string(&config, alsa_config, strlen(alsa_config)); + if (err < 0) { + ksft_print_msg("Unable to parse custom alsa-lib configuration: %s\n", + snd_strerror(err)); + ksft_exit_fail(); + } + return config; +} + static struct card_data *conf_data_by_card(int card, bool msg) { struct card_data *conf; diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index a38b89c28030..05f1749ae19d 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -26,6 +26,7 @@ #include #include "../kselftest.h" +#include "alsa-local.h" #define TESTS_PER_CONTROL 7 @@ -50,56 +51,11 @@ struct ctl_data { struct ctl_data *next; }; -static const char *alsa_config = -"ctl.hw {\n" -" @args [ CARD ]\n" -" @args.CARD.type string\n" -" type hw\n" -" card $CARD\n" -"}\n" -; - int num_cards = 0; int num_controls = 0; struct card_data *card_list = NULL; struct ctl_data *ctl_list = NULL; -#ifdef SND_LIB_VER -#if SND_LIB_VERSION >= SND_LIB_VER(1, 2, 6) -#define LIB_HAS_LOAD_STRING -#endif -#endif - -#ifndef LIB_HAS_LOAD_STRING -static int snd_config_load_string(snd_config_t **config, const char *s, - size_t size) -{ - snd_input_t *input; - snd_config_t *dst; - int err; - - assert(config && s); - if (size == 0) - size = strlen(s); - err = snd_input_buffer_open(&input, s, size); - if (err < 0) - return err; - err = snd_config_top(&dst); - if (err < 0) { - snd_input_close(input); - return err; - } - err = snd_config_load(dst, input); - snd_input_close(input); - if (err < 0) { - snd_config_delete(dst); - return err; - } - *config = dst; - return 0; -} -#endif - static void find_controls(void) { char name[32]; @@ -112,12 +68,7 @@ static void find_controls(void) if (snd_card_next(&card) < 0 || card < 0) return; - err = snd_config_load_string(&config, alsa_config, strlen(alsa_config)); - if (err < 0) { - ksft_print_msg("Unable to parse custom alsa-lib configuration: %s\n", - snd_strerror(err)); - ksft_exit_fail(); - } + config = get_alsalib_config(); while (card >= 0) { sprintf(name, "hw:%d", card); diff --git a/tools/testing/selftests/alsa/pcm-test.c b/tools/testing/selftests/alsa/pcm-test.c index 2814d8f74f82..6e7dfc395b98 100644 --- a/tools/testing/selftests/alsa/pcm-test.c +++ b/tools/testing/selftests/alsa/pcm-test.c @@ -31,26 +31,6 @@ struct pcm_data { struct pcm_data *next; }; -static const char *alsa_config = -"ctl.hw {\n" -" @args [ CARD ]\n" -" @args.CARD.type string\n" -" type hw\n" -" card $CARD\n" -"}\n" -"pcm.hw {\n" -" @args [ CARD DEV SUBDEV ]\n" -" @args.CARD.type string\n" -" @args.DEV.type integer\n" -" @args.SUBDEV.type integer\n" -" type hw\n" -" card $CARD\n" -" device $DEV\n" -" subdevice $SUBDEV\n" -"}\n" - -; - int num_pcms = 0; struct pcm_data *pcm_list = NULL; @@ -77,56 +57,6 @@ long long timestamp_diff_ms(timestamp_t *tstamp) return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L); } -#ifdef SND_LIB_VER -#if SND_LIB_VERSION >= SND_LIB_VER(1, 2, 6) -#define LIB_HAS_LOAD_STRING -#endif -#endif - -#ifndef LIB_HAS_LOAD_STRING -static int snd_config_load_string(snd_config_t **config, const char *s, - size_t size) -{ - snd_input_t *input; - snd_config_t *dst; - int err; - - assert(config && s); - if (size == 0) - size = strlen(s); - err = snd_input_buffer_open(&input, s, size); - if (err < 0) - return err; - err = snd_config_top(&dst); - if (err < 0) { - snd_input_close(input); - return err; - } - err = snd_config_load(dst, input); - snd_input_close(input); - if (err < 0) { - snd_config_delete(dst); - return err; - } - *config = dst; - return 0; -} -#endif - -static snd_config_t *get_alsalib_config(void) -{ - snd_config_t *config; - int err; - - err = snd_config_load_string(&config, alsa_config, strlen(alsa_config)); - if (err < 0) { - ksft_print_msg("Unable to parse custom alsa-lib configuration: %s\n", - snd_strerror(err)); - ksft_exit_fail(); - } - return config; -} - static long device_from_id(snd_config_t *node) { const char *id; -- cgit v1.2.3 From 22eeb8f531c117943a3ef9a75d4d40cdbcc66fb1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 1 Dec 2022 17:07:40 +0000 Subject: kselftest/alsa: Refactor pcm-test to list the tests to run in a struct In order to help make the list of tests a bit easier to maintain refactor things so we pass the tests around as a struct with the parameters in, enabling us to add new tests by adding to a table with comments saying what each of the number are. We could also use named initializers if we get more parameters. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20221201170745.1111236-2-broonie@kernel.org Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/pcm-test.c | 53 ++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 20 deletions(-) (limited to 'tools') diff --git a/tools/testing/selftests/alsa/pcm-test.c b/tools/testing/selftests/alsa/pcm-test.c index 6e7dfc395b98..dc36f4099954 100644 --- a/tools/testing/selftests/alsa/pcm-test.c +++ b/tools/testing/selftests/alsa/pcm-test.c @@ -37,6 +37,15 @@ struct pcm_data *pcm_list = NULL; int num_missing = 0; struct pcm_data *pcm_missing = NULL; +struct time_test_def { + const char *cfg_prefix; + const char *format; + long rate; + long channels; + long period_size; + long buffer_size; +}; + void timestamp_now(timestamp_t *tstamp) { if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp)) @@ -220,9 +229,7 @@ static void find_pcms(void) } static void test_pcm_time1(struct pcm_data *data, - const char *cfg_prefix, const char *sformat, - long srate, long schannels, - long speriod_size, long sbuffer_size) + const struct time_test_def *test) { char name[64], key[128], msg[256]; const char *cs; @@ -244,20 +251,20 @@ static void test_pcm_time1(struct pcm_data *data, snd_pcm_hw_params_alloca(&hw_params); snd_pcm_sw_params_alloca(&sw_params); - cs = conf_get_string(data->pcm_config, cfg_prefix, "format", sformat); + cs = conf_get_string(data->pcm_config, test->cfg_prefix, "format", test->format); format = snd_pcm_format_value(cs); if (format == SND_PCM_FORMAT_UNKNOWN) ksft_exit_fail_msg("Wrong format '%s'\n", cs); - rate = conf_get_long(data->pcm_config, cfg_prefix, "rate", srate); - channels = conf_get_long(data->pcm_config, cfg_prefix, "channels", schannels); - period_size = conf_get_long(data->pcm_config, cfg_prefix, "period_size", speriod_size); - buffer_size = conf_get_long(data->pcm_config, cfg_prefix, "buffer_size", sbuffer_size); + rate = conf_get_long(data->pcm_config, test->cfg_prefix, "rate", test->rate); + channels = conf_get_long(data->pcm_config, test->cfg_prefix, "channels", test->channels); + period_size = conf_get_long(data->pcm_config, test->cfg_prefix, "period_size", test->period_size); + buffer_size = conf_get_long(data->pcm_config, test->cfg_prefix, "buffer_size", test->buffer_size); - automatic = strcmp(sformat, snd_pcm_format_name(format)) == 0 && - srate == rate && - schannels == channels && - speriod_size == period_size && - sbuffer_size == buffer_size; + automatic = strcmp(test->format, snd_pcm_format_name(format)) == 0 && + test->rate == rate && + test->channels == channels && + test->period_size == period_size && + test->buffer_size == buffer_size; samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8); if (!samples) @@ -293,7 +300,7 @@ __format: if (automatic && format == SND_PCM_FORMAT_S16_LE) { format = SND_PCM_FORMAT_S32_LE; ksft_print_msg("%s.%d.%d.%d.%s.%s format S16_LE -> S32_LE\n", - cfg_prefix, + test->cfg_prefix, data->card, data->device, data->subdevice, snd_pcm_stream_name(data->stream), snd_pcm_access_name(access)); @@ -362,7 +369,7 @@ __format: } ksft_print_msg("%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n", - cfg_prefix, + test->cfg_prefix, data->card, data->device, data->subdevice, snd_pcm_stream_name(data->stream), snd_pcm_access_name(access), @@ -411,7 +418,7 @@ __format: pass = true; __close: ksft_test_result(pass, "%s.%d.%d.%d.%s%s%s\n", - cfg_prefix, + test->cfg_prefix, data->card, data->device, data->subdevice, snd_pcm_stream_name(data->stream), msg[0] ? " " : "", msg); @@ -420,11 +427,16 @@ __close: snd_pcm_close(handle); } -#define TESTS_PER_PCM 2 +static const struct time_test_def time_tests[] = { + /* name format rate chan period buffer */ + { "test.time1", "S16_LE", 48000, 2, 512, 4096 }, + { "test.time2", "S16_LE", 48000, 2, 24000, 192000 }, +}; int main(void) { struct pcm_data *pcm; + int i; ksft_print_header(); @@ -432,7 +444,7 @@ int main(void) find_pcms(); - ksft_set_plan(num_missing + num_pcms * TESTS_PER_PCM); + ksft_set_plan(num_missing + num_pcms * ARRAY_SIZE(time_tests)); for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) { ksft_test_result(false, "test.missing.%d.%d.%d.%s\n", @@ -441,8 +453,9 @@ int main(void) } for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) { - test_pcm_time1(pcm, "test.time1", "S16_LE", 48000, 2, 512, 4096); - test_pcm_time1(pcm, "test.time2", "S16_LE", 48000, 2, 24000, 192000); + for (i = 0; i < ARRAY_SIZE(time_tests); i++) { + test_pcm_time1(pcm, &time_tests[i]); + } } conf_free(); -- cgit v1.2.3 From f944f8b539ea76bd70348bfc35dfa0780e510c0f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 1 Dec 2022 17:07:41 +0000 Subject: kselftest/alsa: Report failures to set the requested sample rate as skips If constraint selection gives us a sample rate other than the one that we asked for that isn't a failure, that is the device implementing sample rate constraints and advertising that it can't support whatever we asked for. Report such cases as a test skip rather than failure so we don't have false positives. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20221201170745.1111236-3-broonie@kernel.org Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/pcm-test.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'tools') diff --git a/tools/testing/selftests/alsa/pcm-test.c b/tools/testing/selftests/alsa/pcm-test.c index dc36f4099954..edb820fe2ee6 100644 --- a/tools/testing/selftests/alsa/pcm-test.c +++ b/tools/testing/selftests/alsa/pcm-test.c @@ -247,6 +247,7 @@ static void test_pcm_time1(struct pcm_data *data, bool pass = false, automatic = true; snd_pcm_hw_params_t *hw_params; snd_pcm_sw_params_t *sw_params; + bool skip = false; snd_pcm_hw_params_alloca(&hw_params); snd_pcm_sw_params_alloca(&sw_params); @@ -321,7 +322,8 @@ __format: goto __close; } if (rrate != rate) { - snprintf(msg, sizeof(msg), "rate mismatch %ld != %ld", rate, rrate); + snprintf(msg, sizeof(msg), "rate unsupported %ld != %ld", rate, rrate); + skip = true; goto __close; } rperiod_size = period_size; @@ -417,11 +419,20 @@ __format: msg[0] = '\0'; pass = true; __close: - ksft_test_result(pass, "%s.%d.%d.%d.%s%s%s\n", - test->cfg_prefix, - data->card, data->device, data->subdevice, - snd_pcm_stream_name(data->stream), - msg[0] ? " " : "", msg); + if (!skip) { + ksft_test_result(pass, "%s.%d.%d.%d.%s%s%s\n", + test->cfg_prefix, + data->card, data->device, data->subdevice, + snd_pcm_stream_name(data->stream), + msg[0] ? " " : "", msg); + } else { + ksft_test_result_skip("%s.%d.%d.%d.%s%s%s\n", + test->cfg_prefix, + data->card, data->device, + data->subdevice, + snd_pcm_stream_name(data->stream), + msg[0] ? " " : "", msg); + } free(samples); if (handle) snd_pcm_close(handle); -- cgit v1.2.3 From 8370d9b00c929398cc79572a9b584878024ceb59 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 1 Dec 2022 17:07:42 +0000 Subject: kselftest/alsa: Report failures to set the requested channels as skips If constraint selection gives us a number of channels other than the one that we asked for that isn't a failure, that is the device implementing constraints and advertising that it can't support whatever we asked for. Report such cases as a test skip rather than failure so we don't have false positives. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20221201170745.1111236-4-broonie@kernel.org Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/pcm-test.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/testing/selftests/alsa/pcm-test.c b/tools/testing/selftests/alsa/pcm-test.c index edb820fe2ee6..c701cf507a95 100644 --- a/tools/testing/selftests/alsa/pcm-test.c +++ b/tools/testing/selftests/alsa/pcm-test.c @@ -241,6 +241,7 @@ static void test_pcm_time1(struct pcm_data *data, snd_pcm_sframes_t frames; long long ms; long rate, channels, period_size, buffer_size; + unsigned int rchannels; unsigned int rrate; snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold; timestamp_t tstamp; @@ -310,11 +311,17 @@ __format: snd_pcm_format_name(format), snd_strerror(err)); goto __close; } - err = snd_pcm_hw_params_set_channels(handle, hw_params, channels); + rchannels = channels; + err = snd_pcm_hw_params_set_channels_near(handle, hw_params, &rchannels); if (err < 0) { snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err)); goto __close; } + if (rchannels != channels) { + snprintf(msg, sizeof(msg), "channels unsupported %ld != %ld", channels, rchannels); + skip = true; + goto __close; + } rrate = rate; err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0); if (err < 0) { -- cgit v1.2.3 From ae95efd9754c2afceca89635dacba29af794a131 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 1 Dec 2022 17:07:43 +0000 Subject: kselftest/alsa: Don't any configuration in the sample config The values in the one example configuration file we currently have are the default values for the two tests we have so there's no need to actually set them. Comment them out as examples, with a rename for the tests so that we can update the tests in the code more easily. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20221201170745.1111236-5-broonie@kernel.org Signed-off-by: Takashi Iwai --- .../alsa/conf.d/Lenovo_ThinkPad_P1_Gen2.conf | 35 ++++++++++++---------- 1 file changed, 19 insertions(+), 16 deletions(-) (limited to 'tools') diff --git a/tools/testing/selftests/alsa/conf.d/Lenovo_ThinkPad_P1_Gen2.conf b/tools/testing/selftests/alsa/conf.d/Lenovo_ThinkPad_P1_Gen2.conf index 0a83f35d43eb..9eca985e0c08 100644 --- a/tools/testing/selftests/alsa/conf.d/Lenovo_ThinkPad_P1_Gen2.conf +++ b/tools/testing/selftests/alsa/conf.d/Lenovo_ThinkPad_P1_Gen2.conf @@ -39,22 +39,25 @@ card.hda { # pcm.0.0 { PLAYBACK { - test.time1 { - access RW_INTERLEAVED # can be omitted - default - format S16_LE # can be omitted - default - rate 48000 # can be omitted - default - channels 2 # can be omitted - default - period_size 512 - buffer_size 4096 - } - test.time2 { - access RW_INTERLEAVED - format S16_LE - rate 48000 - channels 2 - period_size 24000 - buffer_size 192000 - } + # + # Uncomment to override values for specific tests + # + #test_name1 { + # access RW_INTERLEAVED + # format S16_LE + # rate 48000 + # channels 2 + # period_size 512 + # buffer_size 4096 + #} + #test_name2 { + # access RW_INTERLEAVED + # format S16_LE + # rate 48000 + # channels 2 + # period_size 24000 + # buffer_size 192000 + #} } CAPTURE { # use default tests, check for the presence -- cgit v1.2.3 From ee12040dd53ac9034a782ed80eb8e8e496c05d9b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 1 Dec 2022 17:07:44 +0000 Subject: kselftest/alsa: Provide more meaningful names for tests Rather than just numbering the tests try to provide semi descriptive names for what the tests are trying to cover. This also has the advantage of meaning we can add more tests without having to keep the list of tests ordered by existing number which should make it easier to understand what we're testing and why. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20221201170745.1111236-6-broonie@kernel.org Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/pcm-test.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'tools') diff --git a/tools/testing/selftests/alsa/pcm-test.c b/tools/testing/selftests/alsa/pcm-test.c index c701cf507a95..afa13377481d 100644 --- a/tools/testing/selftests/alsa/pcm-test.c +++ b/tools/testing/selftests/alsa/pcm-test.c @@ -446,9 +446,9 @@ __close: } static const struct time_test_def time_tests[] = { - /* name format rate chan period buffer */ - { "test.time1", "S16_LE", 48000, 2, 512, 4096 }, - { "test.time2", "S16_LE", 48000, 2, 24000, 192000 }, + /* name format rate chan period buffer */ + { "S16.48k.2.small", "S16_LE", 48000, 2, 512, 4096 }, + { "S16.48k.2.big", "S16_LE", 48000, 2, 24000, 192000 }, }; int main(void) -- cgit v1.2.3 From 7d721baea138696d5a6746fb5bce0a510a91bd65 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 1 Dec 2022 17:07:45 +0000 Subject: kselftest/alsa: Add more coverage of sample rates and channel counts Now that we can skip unsupported configurations add some more test cases using that, cover 8kHz, 44.1kHz and 96kHz plus 8kHz mono and 48kHz 6 channel. 44.1kHz is a different clock base to the existing 48kHz tests and may therefore show problems with the clock configuration if only 8kHz based rates are really available (or help diagnose if bad clocking is due to only 44.1kHz based rates being supported). 8kHz mono and 48Hz 6 channel are real world formats and should show if clocking does not account for channel count properly. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20221201170745.1111236-7-broonie@kernel.org Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/pcm-test.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'tools') diff --git a/tools/testing/selftests/alsa/pcm-test.c b/tools/testing/selftests/alsa/pcm-test.c index afa13377481d..f293c7d81009 100644 --- a/tools/testing/selftests/alsa/pcm-test.c +++ b/tools/testing/selftests/alsa/pcm-test.c @@ -446,9 +446,14 @@ __close: } static const struct time_test_def time_tests[] = { - /* name format rate chan period buffer */ - { "S16.48k.2.small", "S16_LE", 48000, 2, 512, 4096 }, - { "S16.48k.2.big", "S16_LE", 48000, 2, 24000, 192000 }, + /* name format rate chan period buffer */ + { "8k.1.big", "S16_LE", 8000, 2, 8000, 32000 }, + { "8k.2.big", "S16_LE", 8000, 2, 8000, 32000 }, + { "44k1.2.big", "S16_LE", 44100, 2, 22050, 192000 }, + { "48k.2.small", "S16_LE", 48000, 2, 512, 4096 }, + { "48k.2.big", "S16_LE", 48000, 2, 24000, 192000 }, + { "48k.6.big", "S16_LE", 48000, 6, 48000, 576000 }, + { "96k.2.big", "S16_LE", 96000, 2, 48000, 192000 }, }; int main(void) -- cgit v1.2.3