172 lines · 4.1 KB · C
RawDownload
// 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