// 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 */ #include "../kselftest_harness.h" #include #include #include #include #include #include #include #include #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