// 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
// 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