root/tools/testing/selftests/alsa/utimer-test.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);
        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