| // 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 |