1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
// 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>
#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);
ASSERT_EQ(ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info), 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);
}
TEST(wrong_timers_test) {
int timer_dev_fd;
int utimer_fd;
size_t i;
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
|