165 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			165 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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
 |