aboutsummaryrefslogtreecommitdiff
path: root/tools/testing/selftests/alsa/utimer-test.c
blob: 32ee3ce577216b84e4cd16f1cbd630f867e5ba29 (plain)
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