Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,5 @@
global-timer
mixer-test
pcm-test
test-pcmtest-driver
utimer-test
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0
#
ifneq ($(shell pkg-config --exists alsa && echo 0 || echo 1),0)
$(error Package alsa not found, please install alsa development package or \
add directory containing `alsa.pc` in PKG_CONFIG_PATH)
endif
CFLAGS += $(shell pkg-config --cflags alsa) $(KHDR_INCLUDES)
LDLIBS += $(shell pkg-config --libs alsa)
ifeq ($(LDLIBS),)
LDLIBS += -lasound
endif
CFLAGS += -L$(OUTPUT) -Wl,-rpath=./
LDLIBS+=-lpthread
OVERRIDE_TARGETS = 1
TEST_GEN_PROGS := mixer-test pcm-test test-pcmtest-driver utimer-test
TEST_GEN_PROGS_EXTENDED := libatest.so global-timer
TEST_FILES := conf.d pcm-test.conf
include ../lib.mk
$(OUTPUT)/libatest.so: conf.c alsa-local.h
$(CC) $(CFLAGS) -shared -fPIC $< $(LDLIBS) -o $@
$(OUTPUT)/%: %.c $(OUTPUT)/libatest.so alsa-local.h
$(CC) $(CFLAGS) $< $(LDLIBS) -latest -o $@
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-2.0
//
// kselftest configuration helpers for the hw specific configuration
//
// Original author: Jaroslav Kysela <perex@perex.cz>
// Copyright (c) 2022 Red Hat Inc.
#ifndef __ALSA_LOCAL_H
#define __ALSA_LOCAL_H
#include <alsa/asoundlib.h>
snd_config_t *get_alsalib_config(void);
snd_config_t *conf_load_from_file(const char *filename);
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);
void conf_get_string_array(snd_config_t *root, const char *key1, const char *key2,
const char **array, int array_size, const char *def);
struct card_cfg_data {
int card;
snd_config_t *config;
const char *filename;
const char *config_id;
struct card_cfg_data *next;
};
extern struct card_cfg_data *conf_cards;
#endif /* __ALSA_LOCAL_H */
@@ -0,0 +1,475 @@
// SPDX-License-Identifier: GPL-2.0
//
// kselftest configuration helpers for the hw specific configuration
//
// Original author: Jaroslav Kysela <perex@perex.cz>
// Copyright (c) 2022 Red Hat Inc.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <dirent.h>
#include <regex.h>
#include <sys/stat.h>
#include "kselftest.h"
#include "alsa-local.h"
#define SYSFS_ROOT "/sys"
struct card_cfg_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_cfg_data *conf_data_by_card(int card, bool msg)
{
struct card_cfg_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 void 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);
}
snd_config_t *conf_load_from_file(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, strerror(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", &regex_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, &regex_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 void assign_card_config(int card, const char *sysfs_card_root)
{
struct card_cfg_data *data;
snd_config_t *sysfs_card_config;
for (data = conf_cards; data; data = data->next) {
snd_config_search(data->config, "sysfs", &sysfs_card_config);
if (!sysfs_match(sysfs_card_root, sysfs_card_config))
continue;
data->card = card;
break;
}
}
static void assign_card_configs(void)
{
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)
assign_card_config(card, fn);
}
}
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;
}
static bool match_config(const char *filename)
{
struct card_cfg_data *data;
snd_config_t *config, *sysfs_config, *card_config, *sysfs_card_config, *node;
snd_config_iterator_t i, next;
config = conf_load_from_file(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);
data = malloc(sizeof(*data));
if (!data)
ksft_exit_fail_msg("Out of memory\n");
data->filename = filename;
data->config = node;
data->card = -1;
if (snd_config_get_id(node, &data->config_id))
ksft_exit_fail_msg("snd_config_get_id failed for card\n");
data->next = conf_cards;
conf_cards = data;
}
return true;
}
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 (match_config(filename))
filename = NULL;
free(filename);
free(namelist[j]);
}
free(namelist);
assign_card_configs();
}
void conf_free(void)
{
struct card_cfg_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_cfg_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;
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 a bool\n", key1, key2);
return !!ret;
}
void conf_get_string_array(snd_config_t *root, const char *key1, const char *key2,
const char **array, int array_size, const char *def)
{
snd_config_t *cfg;
char buf[16];
int ret, index;
ret = conf_get_by_keys(root, key1, key2, &cfg);
if (ret == -ENOENT)
cfg = NULL;
else if (ret < 0)
ksft_exit_fail_msg("key '%s'.'%s' search error: %s\n", key1, key2, snd_strerror(ret));
for (index = 0; index < array_size; index++) {
if (cfg == NULL) {
array[index] = def;
} else {
sprintf(buf, "%i", index);
array[index] = conf_get_string(cfg, buf, NULL, def);
}
}
}
@@ -0,0 +1,84 @@
#
# 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
}
test.time3 {
access RW_INTERLEAVED
format S16_LE
rate 44100
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
# }
#}
}
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This tool is used by the utimer test, and it allows us to
* count the ticks of a global timer in a certain time frame
* (which is set by `timeout` parameter).
*
* Author: Ivan Orlov <ivan.orlov0322@gmail.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <time.h>
static int ticked;
static void async_callback(snd_async_handler_t *ahandler)
{
ticked++;
}
static char timer_name[64];
static void bind_to_timer(int device, int subdevice, int timeout)
{
snd_timer_t *handle;
snd_timer_params_t *params;
snd_async_handler_t *ahandler;
time_t end;
sprintf(timer_name, "hw:CLASS=%d,SCLASS=%d,DEV=%d,SUBDEV=%d",
SND_TIMER_CLASS_GLOBAL, SND_TIMER_SCLASS_NONE,
device, subdevice);
snd_timer_params_alloca(&params);
if (snd_timer_open(&handle, timer_name, SND_TIMER_OPEN_NONBLOCK) < 0) {
perror("Can't open the timer");
exit(EXIT_FAILURE);
}
snd_timer_params_set_auto_start(params, 1);
snd_timer_params_set_ticks(params, 1);
if (snd_timer_params(handle, params) < 0) {
perror("Can't set timer params");
exit(EXIT_FAILURE);
}
if (snd_async_add_timer_handler(&ahandler, handle, async_callback, NULL) < 0) {
perror("Can't create a handler");
exit(EXIT_FAILURE);
}
end = time(NULL) + timeout;
if (snd_timer_start(handle) < 0) {
perror("Failed to start the timer");
exit(EXIT_FAILURE);
}
printf("Timer has started\n");
while (time(NULL) <= end) {
/*
* Waiting for the timeout to elapse. Can't use sleep here, as it gets
* constantly interrupted by the signal from the timer (SIGIO)
*/
}
snd_timer_stop(handle);
snd_timer_close(handle);
}
int main(int argc, char *argv[])
{
int device, subdevice, timeout;
if (argc < 4) {
perror("Usage: %s <device> <subdevice> <timeout>");
return EXIT_FAILURE;
}
setlinebuf(stdout);
device = atoi(argv[1]);
subdevice = atoi(argv[2]);
timeout = atoi(argv[3]);
bind_to_timer(device, subdevice, timeout);
printf("Total ticks count: %d\n", ticked);
return EXIT_SUCCESS;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,671 @@
// SPDX-License-Identifier: GPL-2.0
//
// kselftest for the ALSA PCM API
//
// Original author: Jaroslav Kysela <perex@perex.cz>
// 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 <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <pthread.h>
#include "kselftest.h"
#include "alsa-local.h"
typedef struct timespec timestamp_t;
struct card_data {
int card;
snd_ctl_card_info_t *info;
const char *name;
pthread_t thread;
struct card_data *next;
};
struct card_data *card_list;
struct pcm_data {
snd_pcm_t *handle;
int card;
int device;
int subdevice;
const char *card_name;
snd_pcm_stream_t stream;
snd_config_t *pcm_config;
struct pcm_data *next;
};
struct pcm_data *pcm_list;
int num_missing;
struct pcm_data *pcm_missing;
snd_config_t *default_pcm_config;
/* Lock while reporting results since kselftest doesn't */
pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER;
enum test_class {
TEST_CLASS_DEFAULT,
TEST_CLASS_SYSTEM,
};
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);
}
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];
char *card_name, *card_longname;
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;
struct card_data *card_data;
snd_pcm_info_alloca(&pcm_info);
card = -1;
if (snd_card_next(&card) < 0 || card < 0)
return;
config = get_alsalib_config();
while (card >= 0) {
card_data = calloc(1, sizeof(*card_data));
if (!card_data)
ksft_exit_fail_msg("Out of memory\n");
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;
}
err = snd_card_get_name(card, &card_name);
if (err != 0)
card_name = "Unknown";
err = snd_card_get_longname(card, &card_longname);
if (err != 0)
card_longname = "Unknown";
err = snd_ctl_card_info_malloc(&card_data->info);
if (err != 0)
ksft_exit_fail_msg("Failed to allocate card info: %d\n",
err);
err = snd_ctl_card_info(handle, card_data->info);
if (err == 0) {
card_data->name = snd_ctl_card_info_get_id(card_data->info);
if (!card_data->name)
ksft_print_msg("Failed to get card ID\n");
} else {
ksft_print_msg("Failed to get card info: %d\n", err);
}
if (!card_data->name)
card_data->name = "Unknown";
ksft_print_msg("Card %d/%s - %s (%s)\n", card,
card_data->name, card_name, card_longname);
card_config = conf_by_card(card);
card_data->card = card;
card_data->next = card_list;
card_list = card_data;
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);
ksft_print_msg("%s.0 - %s\n", card_data->name,
snd_pcm_info_get_id(pcm_info));
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->card_name = card_data->name;
pcm_data->stream = stream;
pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL);
pcm_data->next = pcm_list;
pcm_list = pcm_data;
}
}
}
/* 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_time(struct pcm_data *data, enum test_class class,
const char *test_name, snd_config_t *pcm_cfg)
{
char name[64], msg[256];
const int duration_s = 2, margin_ms = 100;
const int duration_ms = duration_s * 1000;
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, old_format;
const char *alt_formats[8];
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;
snd_pcm_hw_params_t *hw_params;
snd_pcm_sw_params_t *sw_params;
const char *test_class_name;
bool skip = true;
const char *desc;
switch (class) {
case TEST_CLASS_DEFAULT:
test_class_name = "default";
break;
case TEST_CLASS_SYSTEM:
test_class_name = "system";
break;
default:
ksft_exit_fail_msg("Unknown test class %d\n", class);
break;
}
desc = conf_get_string(pcm_cfg, "description", NULL, NULL);
if (desc)
ksft_print_msg("%s.%s.%s.%d.%d.%s - %s\n",
test_class_name, test_name,
data->card_name, data->device, data->subdevice,
snd_pcm_stream_name(data->stream),
desc);
snd_pcm_hw_params_alloca(&hw_params);
snd_pcm_sw_params_alloca(&sw_params);
cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE");
format = snd_pcm_format_value(cs);
if (format == SND_PCM_FORMAT_UNKNOWN)
ksft_exit_fail_msg("Wrong format '%s'\n", cs);
conf_get_string_array(pcm_cfg, "alt_formats", NULL,
alt_formats, ARRAY_SIZE(alt_formats), NULL);
rate = conf_get_long(pcm_cfg, "rate", NULL, 48000);
channels = conf_get_long(pcm_cfg, "channels", NULL, 2);
period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096);
buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384);
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;
}
i = -1;
__format:
err = snd_pcm_hw_params_set_format(handle, hw_params, format);
if (err < 0) {
i++;
if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) {
old_format = format;
format = snd_pcm_format_value(alt_formats[i]);
if (format != SND_PCM_FORMAT_UNKNOWN) {
ksft_print_msg("%s.%s.%d.%d.%s.%s format %s -> %s\n",
test_name,
data->card_name, data->device, data->subdevice,
snd_pcm_stream_name(data->stream),
snd_pcm_access_name(access),
snd_pcm_format_name(old_format),
snd_pcm_format_name(format));
samples = realloc(samples, (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);
goto __format;
}
}
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 != %u", 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.%s.%s.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",
test_class_name, test_name,
data->card_name, 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);
/* Set all the params, actually run the test */
skip = false;
timestamp_now(&tstamp);
for (i = 0; i < duration_s; 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 %ld, wrote %li", rate, frames);
goto __close;
}
if (frames < rate) {
snprintf(msg, sizeof(msg),
"expected %ld, wrote %li", rate, frames);
goto __close;
}
} else {
frames = snd_pcm_readi(handle, samples, rate);
if (frames < 0) {
snprintf(msg, sizeof(msg),
"expected %ld, wrote %li", rate, frames);
goto __close;
}
if (frames < rate) {
snprintf(msg, sizeof(msg),
"expected %ld, wrote %li", rate, frames);
goto __close;
}
}
}
snd_pcm_drain(handle);
ms = timestamp_diff_ms(&tstamp);
if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) {
snprintf(msg, sizeof(msg), "time mismatch: expected %dms got %lld", duration_ms, ms);
goto __close;
}
msg[0] = '\0';
pass = true;
__close:
pthread_mutex_lock(&results_lock);
switch (class) {
case TEST_CLASS_SYSTEM:
test_class_name = "system";
/*
* Anything specified as specific to this system
* should always be supported.
*/
ksft_test_result(!skip, "%s.%s.%s.%d.%d.%s.params\n",
test_class_name, test_name,
data->card_name, data->device,
data->subdevice,
snd_pcm_stream_name(data->stream));
break;
default:
break;
}
if (!skip)
ksft_test_result(pass, "%s.%s.%s.%d.%d.%s\n",
test_class_name, test_name,
data->card_name, data->device,
data->subdevice,
snd_pcm_stream_name(data->stream));
else
ksft_test_result_skip("%s.%s.%s.%d.%d.%s\n",
test_class_name, test_name,
data->card_name, data->device,
data->subdevice,
snd_pcm_stream_name(data->stream));
if (msg[0])
ksft_print_msg("%s\n", msg);
pthread_mutex_unlock(&results_lock);
free(samples);
if (handle)
snd_pcm_close(handle);
}
void run_time_tests(struct pcm_data *pcm, enum test_class class,
snd_config_t *cfg)
{
const char *test_name, *test_type;
snd_config_t *pcm_cfg;
snd_config_iterator_t i, next;
if (!cfg)
return;
cfg = conf_get_subtree(cfg, "test", NULL);
if (cfg == NULL)
return;
snd_config_for_each(i, next, cfg) {
pcm_cfg = snd_config_iterator_entry(i);
if (snd_config_get_id(pcm_cfg, &test_name) < 0)
ksft_exit_fail_msg("snd_config_get_id\n");
test_type = conf_get_string(pcm_cfg, "type", NULL, "time");
if (strcmp(test_type, "time") == 0)
test_pcm_time(pcm, class, test_name, pcm_cfg);
else
ksft_exit_fail_msg("unknown test type '%s'\n", test_type);
}
}
void *card_thread(void *data)
{
struct card_data *card = data;
struct pcm_data *pcm;
for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
if (pcm->card != card->card)
continue;
run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config);
run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config);
}
return 0;
}
int main(void)
{
struct card_data *card;
struct card_cfg_data *conf;
struct pcm_data *pcm;
snd_config_t *global_config, *cfg;
int num_pcm_tests = 0, num_tests, num_std_pcm_tests;
int ret;
void *thread_ret;
ksft_print_header();
global_config = conf_load_from_file("pcm-test.conf");
default_pcm_config = conf_get_subtree(global_config, "pcm", NULL);
if (default_pcm_config == NULL)
ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n");
conf_load();
find_pcms();
for (conf = conf_cards; conf; conf = conf->next)
if (conf->card < 0)
num_missing++;
num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL);
for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
num_pcm_tests += num_std_pcm_tests;
cfg = pcm->pcm_config;
if (cfg == NULL)
continue;
/* Setting params is reported as a separate test */
num_tests = conf_get_count(cfg, "test", NULL) * 2;
if (num_tests > 0)
num_pcm_tests += num_tests;
}
ksft_set_plan(num_missing + num_pcm_tests);
for (conf = conf_cards; conf; conf = conf->next)
if (conf->card < 0)
ksft_test_result_fail("test.missing.%s.%s\n",
conf->filename, conf->config_id);
for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {
ksft_test_result(false, "test.missing.%s.%d.%d.%s\n",
pcm->card_name, pcm->device, pcm->subdevice,
snd_pcm_stream_name(pcm->stream));
}
for (card = card_list; card != NULL; card = card->next) {
ret = pthread_create(&card->thread, NULL, card_thread, card);
if (ret != 0) {
ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n",
card->card, ret,
strerror(errno));
}
}
for (card = card_list; card != NULL; card = card->next) {
ret = pthread_join(card->thread, &thread_ret);
if (ret != 0) {
ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n",
card->card, ret,
strerror(errno));
}
}
snd_config_delete(global_config);
conf_free();
ksft_exit_pass();
return 0;
}
@@ -0,0 +1,63 @@
pcm.test.time1 {
description "8kHz mono large periods"
format S16_LE
alt_formats [ S32_LE ]
rate 8000
channels 1
period_size 8000
buffer_size 32000
}
pcm.test.time2 {
description "8kHz stereo large periods"
format S16_LE
alt_formats [ S32_LE ]
rate 8000
channels 2
period_size 8000
buffer_size 32000
}
pcm.test.time3 {
description "44.1kHz stereo large periods"
format S16_LE
alt_formats [ S32_LE ]
rate 44100
channels 2
period_size 22500
buffer_size 192000
}
pcm.test.time4 {
description "48kHz stereo small periods"
format S16_LE
alt_formats [ S32_LE ]
rate 48000
channels 2
period_size 512
buffer_size 4096
}
pcm.test.time5 {
description "48kHz stereo large periods"
format S16_LE
alt_formats [ S32_LE ]
rate 48000
channels 2
period_size 24000
buffer_size 192000
}
pcm.test.time6 {
description "48kHz 6 channel large periods"
format S16_LE
alt_formats [ S32_LE ]
rate 48000
channels 2
period_size 48000
buffer_size 576000
}
pcm.test.time7 {
description "96kHz stereo large periods"
format S16_LE
alt_formats [ S32_LE ]
rate 96000
channels 2
period_size 48000
buffer_size 192000
}
@@ -0,0 +1,330 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This is the test which covers PCM middle layer data transferring using
* the virtual pcm test driver (snd-pcmtest).
*
* Copyright 2023 Ivan Orlov <ivan.orlov0322@gmail.com>
*/
#include <string.h>
#include <alsa/asoundlib.h>
#include "kselftest_harness.h"
#define CH_NUM 4
struct pattern_buf {
char buf[1024];
int len;
};
struct pattern_buf patterns[CH_NUM];
struct pcmtest_test_params {
unsigned long buffer_size;
unsigned long period_size;
unsigned long channels;
unsigned int rate;
snd_pcm_access_t access;
size_t sec_buf_len;
size_t sample_size;
int time;
snd_pcm_format_t format;
};
static int read_patterns(void)
{
FILE *fp, *fpl;
int i;
char pf[64];
char plf[64];
for (i = 0; i < CH_NUM; i++) {
sprintf(plf, "/sys/kernel/debug/pcmtest/fill_pattern%d_len", i);
fpl = fopen(plf, "r");
if (!fpl)
return -1;
fscanf(fpl, "%u", &patterns[i].len);
fclose(fpl);
sprintf(pf, "/sys/kernel/debug/pcmtest/fill_pattern%d", i);
fp = fopen(pf, "r");
if (!fp)
return -1;
fread(patterns[i].buf, 1, patterns[i].len, fp);
fclose(fp);
}
return 0;
}
static int get_test_results(char *debug_name)
{
int result;
FILE *f;
char fname[128];
sprintf(fname, "/sys/kernel/debug/pcmtest/%s", debug_name);
f = fopen(fname, "r");
if (!f) {
printf("Failed to open file\n");
return -1;
}
fscanf(f, "%d", &result);
fclose(f);
return result;
}
static size_t get_sec_buf_len(unsigned int rate, unsigned long channels, snd_pcm_format_t format)
{
return rate * channels * snd_pcm_format_physical_width(format) / 8;
}
static int setup_handle(snd_pcm_t **handle, snd_pcm_sw_params_t *swparams,
snd_pcm_hw_params_t *hwparams, struct pcmtest_test_params *params,
int card, snd_pcm_stream_t stream)
{
char pcm_name[32];
int err;
sprintf(pcm_name, "hw:%d,0,0", card);
err = snd_pcm_open(handle, pcm_name, stream, 0);
if (err < 0)
return err;
snd_pcm_hw_params_any(*handle, hwparams);
snd_pcm_hw_params_set_rate_resample(*handle, hwparams, 0);
snd_pcm_hw_params_set_access(*handle, hwparams, params->access);
snd_pcm_hw_params_set_format(*handle, hwparams, params->format);
snd_pcm_hw_params_set_channels(*handle, hwparams, params->channels);
snd_pcm_hw_params_set_rate_near(*handle, hwparams, &params->rate, 0);
snd_pcm_hw_params_set_period_size_near(*handle, hwparams, &params->period_size, 0);
snd_pcm_hw_params_set_buffer_size_near(*handle, hwparams, &params->buffer_size);
snd_pcm_hw_params(*handle, hwparams);
snd_pcm_sw_params_current(*handle, swparams);
snd_pcm_hw_params_set_rate_resample(*handle, hwparams, 0);
snd_pcm_sw_params_set_avail_min(*handle, swparams, params->period_size);
snd_pcm_hw_params_set_buffer_size_near(*handle, hwparams, &params->buffer_size);
snd_pcm_hw_params_set_period_size_near(*handle, hwparams, &params->period_size, 0);
snd_pcm_sw_params(*handle, swparams);
snd_pcm_hw_params(*handle, hwparams);
return 0;
}
FIXTURE(pcmtest) {
int card;
snd_pcm_sw_params_t *swparams;
snd_pcm_hw_params_t *hwparams;
struct pcmtest_test_params params;
};
FIXTURE_TEARDOWN(pcmtest) {
}
FIXTURE_SETUP(pcmtest) {
char *card_name;
int err;
if (geteuid())
SKIP(return, "This test needs root to run!");
err = read_patterns();
if (err)
SKIP(return, "Can't read patterns. Probably, module isn't loaded");
card_name = malloc(127);
ASSERT_NE(card_name, NULL);
self->params.buffer_size = 16384;
self->params.period_size = 4096;
self->params.channels = CH_NUM;
self->params.rate = 8000;
self->params.access = SND_PCM_ACCESS_RW_INTERLEAVED;
self->params.format = SND_PCM_FORMAT_S16_LE;
self->card = -1;
self->params.sample_size = snd_pcm_format_physical_width(self->params.format) / 8;
self->params.sec_buf_len = get_sec_buf_len(self->params.rate, self->params.channels,
self->params.format);
self->params.time = 4;
while (snd_card_next(&self->card) >= 0) {
if (self->card == -1)
break;
snd_card_get_name(self->card, &card_name);
if (!strcmp(card_name, "PCM-Test"))
break;
}
free(card_name);
ASSERT_NE(self->card, -1);
}
/*
* Here we are trying to send the looped monotonically increasing sequence of bytes to the driver.
* If our data isn't corrupted, the driver will set the content of 'pc_test' debugfs file to '1'
*/
TEST_F(pcmtest, playback) {
snd_pcm_t *handle;
unsigned char *it;
size_t write_res;
int test_results;
int i, cur_ch, pos_in_ch;
void *samples;
struct pcmtest_test_params *params = &self->params;
samples = calloc(self->params.sec_buf_len * self->params.time, 1);
ASSERT_NE(samples, NULL);
snd_pcm_sw_params_alloca(&self->swparams);
snd_pcm_hw_params_alloca(&self->hwparams);
ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params,
self->card, SND_PCM_STREAM_PLAYBACK), 0);
snd_pcm_format_set_silence(params->format, samples,
params->rate * params->channels * params->time);
it = samples;
for (i = 0; i < self->params.sec_buf_len * params->time; i++) {
cur_ch = (i / params->sample_size) % CH_NUM;
pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size
+ (i % params->sample_size);
it[i] = patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len];
}
write_res = snd_pcm_writei(handle, samples, params->rate * params->time);
ASSERT_GE(write_res, 0);
snd_pcm_close(handle);
free(samples);
test_results = get_test_results("pc_test");
ASSERT_EQ(test_results, 1);
}
/*
* Here we test that the virtual alsa driver returns looped and monotonically increasing sequence
* of bytes. In the interleaved mode the buffer will contain samples in the following order:
* C0, C1, C2, C3, C0, C1, ...
*/
TEST_F(pcmtest, capture) {
snd_pcm_t *handle;
unsigned char *it;
size_t read_res;
int i, cur_ch, pos_in_ch;
void *samples;
struct pcmtest_test_params *params = &self->params;
samples = calloc(self->params.sec_buf_len * self->params.time, 1);
ASSERT_NE(samples, NULL);
snd_pcm_sw_params_alloca(&self->swparams);
snd_pcm_hw_params_alloca(&self->hwparams);
ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams,
params, self->card, SND_PCM_STREAM_CAPTURE), 0);
snd_pcm_format_set_silence(params->format, samples,
params->rate * params->channels * params->time);
read_res = snd_pcm_readi(handle, samples, params->rate * params->time);
ASSERT_GE(read_res, 0);
snd_pcm_close(handle);
it = (unsigned char *)samples;
for (i = 0; i < self->params.sec_buf_len * self->params.time; i++) {
cur_ch = (i / params->sample_size) % CH_NUM;
pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size
+ (i % params->sample_size);
ASSERT_EQ(it[i], patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]);
}
free(samples);
}
// Test capture in the non-interleaved access mode. The are buffers for each recorded channel
TEST_F(pcmtest, ni_capture) {
snd_pcm_t *handle;
struct pcmtest_test_params params = self->params;
char **chan_samples;
size_t i, j, read_res;
chan_samples = calloc(CH_NUM, sizeof(*chan_samples));
ASSERT_NE(chan_samples, NULL);
snd_pcm_sw_params_alloca(&self->swparams);
snd_pcm_hw_params_alloca(&self->hwparams);
params.access = SND_PCM_ACCESS_RW_NONINTERLEAVED;
ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams,
&params, self->card, SND_PCM_STREAM_CAPTURE), 0);
for (i = 0; i < CH_NUM; i++)
chan_samples[i] = calloc(params.sec_buf_len * params.time, 1);
for (i = 0; i < 1; i++) {
read_res = snd_pcm_readn(handle, (void **)chan_samples, params.rate * params.time);
ASSERT_GE(read_res, 0);
}
snd_pcm_close(handle);
for (i = 0; i < CH_NUM; i++) {
for (j = 0; j < params.rate * params.time; j++)
ASSERT_EQ(chan_samples[i][j], patterns[i].buf[j % patterns[i].len]);
free(chan_samples[i]);
}
free(chan_samples);
}
TEST_F(pcmtest, ni_playback) {
snd_pcm_t *handle;
struct pcmtest_test_params params = self->params;
char **chan_samples;
size_t i, j, read_res;
int test_res;
chan_samples = calloc(CH_NUM, sizeof(*chan_samples));
ASSERT_NE(chan_samples, NULL);
snd_pcm_sw_params_alloca(&self->swparams);
snd_pcm_hw_params_alloca(&self->hwparams);
params.access = SND_PCM_ACCESS_RW_NONINTERLEAVED;
ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams,
&params, self->card, SND_PCM_STREAM_PLAYBACK), 0);
for (i = 0; i < CH_NUM; i++) {
chan_samples[i] = calloc(params.sec_buf_len * params.time, 1);
for (j = 0; j < params.sec_buf_len * params.time; j++)
chan_samples[i][j] = patterns[i].buf[j % patterns[i].len];
}
for (i = 0; i < 1; i++) {
read_res = snd_pcm_writen(handle, (void **)chan_samples, params.rate * params.time);
ASSERT_GE(read_res, 0);
}
snd_pcm_close(handle);
test_res = get_test_results("pc_test");
ASSERT_EQ(test_res, 1);
for (i = 0; i < CH_NUM; i++)
free(chan_samples[i]);
free(chan_samples);
}
/*
* Here we are testing the custom ioctl definition inside the virtual driver. If it triggers
* successfully, the driver sets the content of 'ioctl_test' debugfs file to '1'.
*/
TEST_F(pcmtest, reset_ioctl) {
snd_pcm_t *handle;
int test_res;
struct pcmtest_test_params *params = &self->params;
snd_pcm_sw_params_alloca(&self->swparams);
snd_pcm_hw_params_alloca(&self->hwparams);
ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params,
self->card, SND_PCM_STREAM_CAPTURE), 0);
snd_pcm_reset(handle);
test_res = get_test_results("ioctl_test");
ASSERT_EQ(test_res, 1);
snd_pcm_close(handle);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This test covers the functionality of userspace-driven ALSA timers. Such timers
* are purely virtual (so they don't directly depend on the hardware), and they could be
* created and triggered by userspace applications.
*
* Author: Ivan Orlov <ivan.orlov0322@gmail.com>
*/
#include "kselftest_harness.h"
#include <sound/asound.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#define FRAME_RATE 8000
#define PERIOD_SIZE 4410
#define UTIMER_DEFAULT_ID -1
#define UTIMER_DEFAULT_FD -1
#define NANO 1000000000ULL
#define TICKS_COUNT 10
#define TICKS_RECORDING_DELTA 5
#define TIMER_OUTPUT_BUF_LEN 1024
#define TIMER_FREQ_SEC 1
#define RESULT_PREFIX_LEN strlen("Total ticks count: ")
enum timer_app_event {
TIMER_APP_STARTED,
TIMER_APP_RESULT,
TIMER_NO_EVENT,
};
FIXTURE(timer_f) {
struct snd_timer_uinfo *utimer_info;
};
FIXTURE_SETUP(timer_f) {
int timer_dev_fd;
if (geteuid())
SKIP(return, "This test needs root to run!");
self->utimer_info = calloc(1, sizeof(*self->utimer_info));
ASSERT_NE(NULL, self->utimer_info);
/* Resolution is the time the period of frames takes in nanoseconds */
self->utimer_info->resolution = (NANO / FRAME_RATE * PERIOD_SIZE);
timer_dev_fd = open("/dev/snd/timer", O_RDONLY);
ASSERT_GE(timer_dev_fd, 0);
if (ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info) < 0) {
int err = errno;
close(timer_dev_fd);
if (err == ENOTTY || err == ENXIO)
SKIP(return, "CONFIG_SND_UTIMER not enabled");
ASSERT_EQ(err, 0);
}
ASSERT_GE(self->utimer_info->fd, 0);
close(timer_dev_fd);
}
FIXTURE_TEARDOWN(timer_f) {
close(self->utimer_info->fd);
free(self->utimer_info);
}
static void *ticking_func(void *data)
{
int i;
int *fd = (int *)data;
for (i = 0; i < TICKS_COUNT; i++) {
/* Well, trigger the timer! */
ioctl(*fd, SNDRV_TIMER_IOCTL_TRIGGER, NULL);
sleep(TIMER_FREQ_SEC);
}
return NULL;
}
static enum timer_app_event parse_timer_output(const char *s)
{
if (strstr(s, "Timer has started"))
return TIMER_APP_STARTED;
if (strstr(s, "Total ticks count"))
return TIMER_APP_RESULT;
return TIMER_NO_EVENT;
}
static int parse_timer_result(const char *s)
{
char *end;
long d;
d = strtol(s + RESULT_PREFIX_LEN, &end, 10);
if (end == s + RESULT_PREFIX_LEN)
return -1;
return d;
}
/*
* This test triggers the timer and counts ticks at the same time. The amount
* of the timer trigger calls should be equal to the amount of ticks received.
*/
TEST_F(timer_f, utimer) {
char command[64];
pthread_t ticking_thread;
int total_ticks = 0;
FILE *rfp;
char *buf = malloc(TIMER_OUTPUT_BUF_LEN);
ASSERT_NE(buf, NULL);
/* The timeout should be the ticks interval * count of ticks + some delta */
sprintf(command, "./global-timer %d %d %d", SNDRV_TIMER_GLOBAL_UDRIVEN,
self->utimer_info->id, TICKS_COUNT * TIMER_FREQ_SEC + TICKS_RECORDING_DELTA);
rfp = popen(command, "r");
while (fgets(buf, TIMER_OUTPUT_BUF_LEN, rfp)) {
buf[TIMER_OUTPUT_BUF_LEN - 1] = 0;
switch (parse_timer_output(buf)) {
case TIMER_APP_STARTED:
/* global-timer waits for timer to trigger, so start the ticking thread */
pthread_create(&ticking_thread, NULL, ticking_func,
&self->utimer_info->fd);
break;
case TIMER_APP_RESULT:
total_ticks = parse_timer_result(buf);
break;
case TIMER_NO_EVENT:
break;
}
}
pthread_join(ticking_thread, NULL);
ASSERT_EQ(total_ticks, TICKS_COUNT);
pclose(rfp);
free(buf);
}
TEST(wrong_timers_test) {
int timer_dev_fd;
int utimer_fd;
struct snd_timer_uinfo wrong_timer = {
.resolution = 0,
.id = UTIMER_DEFAULT_ID,
.fd = UTIMER_DEFAULT_FD,
};
timer_dev_fd = open("/dev/snd/timer", O_RDONLY);
ASSERT_GE(timer_dev_fd, 0);
utimer_fd = ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, &wrong_timer);
ASSERT_LT(utimer_fd, 0);
/* Check that id was not updated */
ASSERT_EQ(wrong_timer.id, UTIMER_DEFAULT_ID);
/* Test the NULL as an argument is processed correctly */
ASSERT_LT(ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, NULL), 0);
close(timer_dev_fd);
}
TEST_HARNESS_MAIN