root/tools/tracing/rtla/src/osnoise.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
 */

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sched.h>

#include "osnoise.h"

#define DEFAULT_SAMPLE_PERIOD   1000000                 /* 1s */
#define DEFAULT_SAMPLE_RUNTIME  1000000                 /* 1s */

/*
 * osnoise_get_cpus - return the original "osnoise/cpus" content
 *
 * It also saves the value to be restored.
 */
char *osnoise_get_cpus(struct osnoise_context *context)
{
        if (context->curr_cpus)
                return context->curr_cpus;

        if (context->orig_cpus)
                return context->orig_cpus;

        context->orig_cpus = tracefs_instance_file_read(NULL, "osnoise/cpus", NULL);

        /*
         * The error value (NULL) is the same for tracefs_instance_file_read()
         * and this functions, so:
         */
        return context->orig_cpus;
}

/*
 * osnoise_set_cpus - configure osnoise to run on *cpus
 *
 * "osnoise/cpus" file is used to set the cpus in which osnoise/timerlat
 * will run. This function opens this file, saves the current value,
 * and set the cpus passed as argument.
 */
int osnoise_set_cpus(struct osnoise_context *context, char *cpus)
{
        char *orig_cpus = osnoise_get_cpus(context);
        char buffer[1024];
        int retval;

        if (!orig_cpus)
                return -1;

        context->curr_cpus = strdup(cpus);
        if (!context->curr_cpus)
                return -1;

        snprintf(buffer, 1024, "%s\n", cpus);

        debug_msg("setting cpus to %s from %s", cpus, context->orig_cpus);

        retval = tracefs_instance_file_write(NULL, "osnoise/cpus", buffer);
        if (retval < 0) {
                free(context->curr_cpus);
                context->curr_cpus = NULL;
                return -1;
        }

        return 0;
}

/*
 * osnoise_restore_cpus - restore the original "osnoise/cpus"
 *
 * osnoise_set_cpus() saves the original data for the "osnoise/cpus"
 * file. This function restore the original config it was previously
 * modified.
 */
void osnoise_restore_cpus(struct osnoise_context *context)
{
        int retval;

        if (!context->orig_cpus)
                return;

        if (!context->curr_cpus)
                return;

        /* nothing to do? */
        if (!strcmp(context->orig_cpus, context->curr_cpus))
                goto out_done;

        debug_msg("restoring cpus to %s", context->orig_cpus);

        retval = tracefs_instance_file_write(NULL, "osnoise/cpus", context->orig_cpus);
        if (retval < 0)
                err_msg("could not restore original osnoise cpus\n");

out_done:
        free(context->curr_cpus);
        context->curr_cpus = NULL;
}

/*
 * osnoise_put_cpus - restore cpus config and cleanup data
 */
void osnoise_put_cpus(struct osnoise_context *context)
{
        osnoise_restore_cpus(context);

        if (!context->orig_cpus)
                return;

        free(context->orig_cpus);
        context->orig_cpus = NULL;
}

/*
 * osnoise_read_ll_config - read a long long value from a config
 *
 * returns -1 on error.
 */
static long long osnoise_read_ll_config(char *rel_path)
{
        long long retval;
        char *buffer;

        buffer = tracefs_instance_file_read(NULL, rel_path, NULL);
        if (!buffer)
                return -1;

        /* get_llong_from_str returns -1 on error */
        retval = get_llong_from_str(buffer);

        debug_msg("reading %s returned %lld\n", rel_path, retval);

        free(buffer);

        return retval;
}

/*
 * osnoise_write_ll_config - write a long long value to a config in rel_path
 *
 * returns -1 on error.
 */
static long long osnoise_write_ll_config(char *rel_path, long long value)
{
        char buffer[BUFF_U64_STR_SIZE];
        long long retval;

        snprintf(buffer, sizeof(buffer), "%lld\n", value);

        debug_msg("setting %s to %lld\n", rel_path, value);

        retval = tracefs_instance_file_write(NULL, rel_path, buffer);
        return retval;
}

/*
 * osnoise_get_runtime - return the original "osnoise/runtime_us" value
 *
 * It also saves the value to be restored.
 */
unsigned long long osnoise_get_runtime(struct osnoise_context *context)
{
        long long runtime_us;

        if (context->runtime_us != OSNOISE_TIME_INIT_VAL)
                return context->runtime_us;

        if (context->orig_runtime_us != OSNOISE_TIME_INIT_VAL)
                return context->orig_runtime_us;

        runtime_us = osnoise_read_ll_config("osnoise/runtime_us");
        if (runtime_us < 0)
                goto out_err;

        context->orig_runtime_us = runtime_us;
        return runtime_us;

out_err:
        return OSNOISE_TIME_INIT_VAL;
}

/*
 * osnoise_get_period - return the original "osnoise/period_us" value
 *
 * It also saves the value to be restored.
 */
unsigned long long osnoise_get_period(struct osnoise_context *context)
{
        long long period_us;

        if (context->period_us != OSNOISE_TIME_INIT_VAL)
                return context->period_us;

        if (context->orig_period_us != OSNOISE_TIME_INIT_VAL)
                return context->orig_period_us;

        period_us = osnoise_read_ll_config("osnoise/period_us");
        if (period_us < 0)
                goto out_err;

        context->orig_period_us = period_us;
        return period_us;

out_err:
        return OSNOISE_TIME_INIT_VAL;
}

static int __osnoise_write_runtime(struct osnoise_context *context,
                                   unsigned long long runtime)
{
        int retval;

        if (context->orig_runtime_us == OSNOISE_TIME_INIT_VAL)
                return -1;

        retval = osnoise_write_ll_config("osnoise/runtime_us", runtime);
        if (retval < 0)
                return -1;

        context->runtime_us = runtime;
        return 0;
}

static int __osnoise_write_period(struct osnoise_context *context,
                                  unsigned long long period)
{
        int retval;

        if (context->orig_period_us == OSNOISE_TIME_INIT_VAL)
                return -1;

        retval = osnoise_write_ll_config("osnoise/period_us", period);
        if (retval < 0)
                return -1;

        context->period_us = period;
        return 0;
}

/*
 * osnoise_set_runtime_period - set osnoise runtime and period
 *
 * Osnoise's runtime and period are related as runtime <= period.
 * Thus, this function saves the original values, and then tries
 * to set the runtime and period if they are != 0.
 */
int osnoise_set_runtime_period(struct osnoise_context *context,
                               unsigned long long runtime,
                               unsigned long long period)
{
        unsigned long long curr_runtime_us;
        unsigned long long curr_period_us;
        int retval;

        if (!period && !runtime)
                return 0;

        curr_runtime_us = osnoise_get_runtime(context);
        curr_period_us = osnoise_get_period(context);

        /* error getting any value? */
        if (curr_period_us == OSNOISE_TIME_INIT_VAL || curr_runtime_us == OSNOISE_TIME_INIT_VAL)
                return -1;

        if (!period) {
                if (runtime > curr_period_us)
                        return -1;
                return __osnoise_write_runtime(context, runtime);
        } else if (!runtime) {
                if (period < curr_runtime_us)
                        return -1;
                return __osnoise_write_period(context, period);
        }

        if (runtime > curr_period_us) {
                retval = __osnoise_write_period(context, period);
                if (retval)
                        return -1;
                retval = __osnoise_write_runtime(context, runtime);
                if (retval)
                        return -1;
        } else {
                retval = __osnoise_write_runtime(context, runtime);
                if (retval)
                        return -1;
                retval = __osnoise_write_period(context, period);
                if (retval)
                        return -1;
        }

        return 0;
}

/*
 * osnoise_restore_runtime_period - restore the original runtime and period
 */
void osnoise_restore_runtime_period(struct osnoise_context *context)
{
        unsigned long long orig_runtime = context->orig_runtime_us;
        unsigned long long orig_period = context->orig_period_us;
        unsigned long long curr_runtime = context->runtime_us;
        unsigned long long curr_period = context->period_us;
        int retval;

        if ((orig_runtime == OSNOISE_TIME_INIT_VAL) && (orig_period == OSNOISE_TIME_INIT_VAL))
                return;

        if ((orig_period == curr_period) && (orig_runtime == curr_runtime))
                goto out_done;

        retval = osnoise_set_runtime_period(context, orig_runtime, orig_period);
        if (retval)
                err_msg("Could not restore original osnoise runtime/period\n");

out_done:
        context->runtime_us = OSNOISE_TIME_INIT_VAL;
        context->period_us = OSNOISE_TIME_INIT_VAL;
}

/*
 * osnoise_put_runtime_period - restore original values and cleanup data
 */
void osnoise_put_runtime_period(struct osnoise_context *context)
{
        osnoise_restore_runtime_period(context);

        if (context->orig_runtime_us != OSNOISE_TIME_INIT_VAL)
                context->orig_runtime_us = OSNOISE_TIME_INIT_VAL;

        if (context->orig_period_us != OSNOISE_TIME_INIT_VAL)
                context->orig_period_us = OSNOISE_TIME_INIT_VAL;
}

/*
 * osnoise_get_timerlat_period_us - read and save the original "timerlat_period_us"
 */
static long long
osnoise_get_timerlat_period_us(struct osnoise_context *context)
{
        long long timerlat_period_us;

        if (context->timerlat_period_us != OSNOISE_TIME_INIT_VAL)
                return context->timerlat_period_us;

        if (context->orig_timerlat_period_us != OSNOISE_TIME_INIT_VAL)
                return context->orig_timerlat_period_us;

        timerlat_period_us = osnoise_read_ll_config("osnoise/timerlat_period_us");
        if (timerlat_period_us < 0)
                goto out_err;

        context->orig_timerlat_period_us = timerlat_period_us;
        return timerlat_period_us;

out_err:
        return OSNOISE_TIME_INIT_VAL;
}

/*
 * osnoise_set_timerlat_period_us - set "timerlat_period_us"
 */
int osnoise_set_timerlat_period_us(struct osnoise_context *context, long long timerlat_period_us)
{
        long long curr_timerlat_period_us = osnoise_get_timerlat_period_us(context);
        int retval;

        if (curr_timerlat_period_us == OSNOISE_TIME_INIT_VAL)
                return -1;

        retval = osnoise_write_ll_config("osnoise/timerlat_period_us", timerlat_period_us);
        if (retval < 0)
                return -1;

        context->timerlat_period_us = timerlat_period_us;

        return 0;
}

/*
 * osnoise_restore_timerlat_period_us - restore "timerlat_period_us"
 */
void osnoise_restore_timerlat_period_us(struct osnoise_context *context)
{
        int retval;

        if (context->orig_timerlat_period_us == OSNOISE_TIME_INIT_VAL)
                return;

        if (context->orig_timerlat_period_us == context->timerlat_period_us)
                goto out_done;

        retval = osnoise_write_ll_config("osnoise/timerlat_period_us", context->orig_timerlat_period_us);
        if (retval < 0)
                err_msg("Could not restore original osnoise timerlat_period_us\n");

out_done:
        context->timerlat_period_us = OSNOISE_TIME_INIT_VAL;
}

/*
 * osnoise_put_timerlat_period_us - restore original values and cleanup data
 */
void osnoise_put_timerlat_period_us(struct osnoise_context *context)
{
        osnoise_restore_timerlat_period_us(context);

        if (context->orig_timerlat_period_us == OSNOISE_TIME_INIT_VAL)
                return;

        context->orig_timerlat_period_us = OSNOISE_TIME_INIT_VAL;
}

/*
 * osnoise_get_stop_us - read and save the original "stop_tracing_us"
 */
static long long
osnoise_get_stop_us(struct osnoise_context *context)
{
        long long stop_us;

        if (context->stop_us != OSNOISE_OPTION_INIT_VAL)
                return context->stop_us;

        if (context->orig_stop_us != OSNOISE_OPTION_INIT_VAL)
                return context->orig_stop_us;

        stop_us = osnoise_read_ll_config("osnoise/stop_tracing_us");
        if (stop_us < 0)
                goto out_err;

        context->orig_stop_us = stop_us;
        return stop_us;

out_err:
        return OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_set_stop_us - set "stop_tracing_us"
 */
int osnoise_set_stop_us(struct osnoise_context *context, long long stop_us)
{
        long long curr_stop_us = osnoise_get_stop_us(context);
        int retval;

        if (curr_stop_us == OSNOISE_OPTION_INIT_VAL)
                return -1;

        retval = osnoise_write_ll_config("osnoise/stop_tracing_us", stop_us);
        if (retval < 0)
                return -1;

        context->stop_us = stop_us;

        return 0;
}

/*
 * osnoise_restore_stop_us - restore the original "stop_tracing_us"
 */
void osnoise_restore_stop_us(struct osnoise_context *context)
{
        int retval;

        if (context->orig_stop_us == OSNOISE_OPTION_INIT_VAL)
                return;

        if (context->orig_stop_us == context->stop_us)
                goto out_done;

        retval = osnoise_write_ll_config("osnoise/stop_tracing_us", context->orig_stop_us);
        if (retval < 0)
                err_msg("Could not restore original osnoise stop_us\n");

out_done:
        context->stop_us = OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_put_stop_us - restore original values and cleanup data
 */
void osnoise_put_stop_us(struct osnoise_context *context)
{
        osnoise_restore_stop_us(context);

        if (context->orig_stop_us == OSNOISE_OPTION_INIT_VAL)
                return;

        context->orig_stop_us = OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_get_stop_total_us - read and save the original "stop_tracing_total_us"
 */
static long long
osnoise_get_stop_total_us(struct osnoise_context *context)
{
        long long stop_total_us;

        if (context->stop_total_us != OSNOISE_OPTION_INIT_VAL)
                return context->stop_total_us;

        if (context->orig_stop_total_us != OSNOISE_OPTION_INIT_VAL)
                return context->orig_stop_total_us;

        stop_total_us = osnoise_read_ll_config("osnoise/stop_tracing_total_us");
        if (stop_total_us < 0)
                goto out_err;

        context->orig_stop_total_us = stop_total_us;
        return stop_total_us;

out_err:
        return OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_set_stop_total_us - set "stop_tracing_total_us"
 */
int osnoise_set_stop_total_us(struct osnoise_context *context, long long stop_total_us)
{
        long long curr_stop_total_us = osnoise_get_stop_total_us(context);
        int retval;

        if (curr_stop_total_us == OSNOISE_OPTION_INIT_VAL)
                return -1;

        retval = osnoise_write_ll_config("osnoise/stop_tracing_total_us", stop_total_us);
        if (retval < 0)
                return -1;

        context->stop_total_us = stop_total_us;

        return 0;
}

/*
 * osnoise_restore_stop_total_us - restore the original "stop_tracing_total_us"
 */
void osnoise_restore_stop_total_us(struct osnoise_context *context)
{
        int retval;

        if (context->orig_stop_total_us == OSNOISE_OPTION_INIT_VAL)
                return;

        if (context->orig_stop_total_us == context->stop_total_us)
                goto out_done;

        retval = osnoise_write_ll_config("osnoise/stop_tracing_total_us",
                        context->orig_stop_total_us);
        if (retval < 0)
                err_msg("Could not restore original osnoise stop_total_us\n");

out_done:
        context->stop_total_us = OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_put_stop_total_us - restore original values and cleanup data
 */
void osnoise_put_stop_total_us(struct osnoise_context *context)
{
        osnoise_restore_stop_total_us(context);

        if (context->orig_stop_total_us == OSNOISE_OPTION_INIT_VAL)
                return;

        context->orig_stop_total_us = OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_get_print_stack - read and save the original "print_stack"
 */
static long long
osnoise_get_print_stack(struct osnoise_context *context)
{
        long long print_stack;

        if (context->print_stack != OSNOISE_OPTION_INIT_VAL)
                return context->print_stack;

        if (context->orig_print_stack != OSNOISE_OPTION_INIT_VAL)
                return context->orig_print_stack;

        print_stack = osnoise_read_ll_config("osnoise/print_stack");
        if (print_stack < 0)
                goto out_err;

        context->orig_print_stack = print_stack;
        return print_stack;

out_err:
        return OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_set_print_stack - set "print_stack"
 */
int osnoise_set_print_stack(struct osnoise_context *context, long long print_stack)
{
        long long curr_print_stack = osnoise_get_print_stack(context);
        int retval;

        if (curr_print_stack == OSNOISE_OPTION_INIT_VAL)
                return -1;

        retval = osnoise_write_ll_config("osnoise/print_stack", print_stack);
        if (retval < 0)
                return -1;

        context->print_stack = print_stack;

        return 0;
}

/*
 * osnoise_restore_print_stack - restore the original "print_stack"
 */
void osnoise_restore_print_stack(struct osnoise_context *context)
{
        int retval;

        if (context->orig_print_stack == OSNOISE_OPTION_INIT_VAL)
                return;

        if (context->orig_print_stack == context->print_stack)
                goto out_done;

        retval = osnoise_write_ll_config("osnoise/print_stack", context->orig_print_stack);
        if (retval < 0)
                err_msg("Could not restore original osnoise print_stack\n");

out_done:
        context->print_stack = OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_put_print_stack - restore original values and cleanup data
 */
void osnoise_put_print_stack(struct osnoise_context *context)
{
        osnoise_restore_print_stack(context);

        if (context->orig_print_stack == OSNOISE_OPTION_INIT_VAL)
                return;

        context->orig_print_stack = OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_get_tracing_thresh - read and save the original "tracing_thresh"
 */
static long long
osnoise_get_tracing_thresh(struct osnoise_context *context)
{
        long long tracing_thresh;

        if (context->tracing_thresh != OSNOISE_OPTION_INIT_VAL)
                return context->tracing_thresh;

        if (context->orig_tracing_thresh != OSNOISE_OPTION_INIT_VAL)
                return context->orig_tracing_thresh;

        tracing_thresh = osnoise_read_ll_config("tracing_thresh");
        if (tracing_thresh < 0)
                goto out_err;

        context->orig_tracing_thresh = tracing_thresh;
        return tracing_thresh;

out_err:
        return OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_set_tracing_thresh - set "tracing_thresh"
 */
int osnoise_set_tracing_thresh(struct osnoise_context *context, long long tracing_thresh)
{
        long long curr_tracing_thresh = osnoise_get_tracing_thresh(context);
        int retval;

        if (curr_tracing_thresh == OSNOISE_OPTION_INIT_VAL)
                return -1;

        retval = osnoise_write_ll_config("tracing_thresh", tracing_thresh);
        if (retval < 0)
                return -1;

        context->tracing_thresh = tracing_thresh;

        return 0;
}

/*
 * osnoise_restore_tracing_thresh - restore the original "tracing_thresh"
 */
void osnoise_restore_tracing_thresh(struct osnoise_context *context)
{
        int retval;

        if (context->orig_tracing_thresh == OSNOISE_OPTION_INIT_VAL)
                return;

        if (context->orig_tracing_thresh == context->tracing_thresh)
                goto out_done;

        retval = osnoise_write_ll_config("tracing_thresh", context->orig_tracing_thresh);
        if (retval < 0)
                err_msg("Could not restore original tracing_thresh\n");

out_done:
        context->tracing_thresh = OSNOISE_OPTION_INIT_VAL;
}

/*
 * osnoise_put_tracing_thresh - restore original values and cleanup data
 */
void osnoise_put_tracing_thresh(struct osnoise_context *context)
{
        osnoise_restore_tracing_thresh(context);

        if (context->orig_tracing_thresh == OSNOISE_OPTION_INIT_VAL)
                return;

        context->orig_tracing_thresh = OSNOISE_OPTION_INIT_VAL;
}

static int osnoise_options_get_option(char *option)
{
        char *options = tracefs_instance_file_read(NULL, "osnoise/options", NULL);
        char no_option[128];
        int retval = 0;
        char *opt;

        if (!options)
                return OSNOISE_OPTION_INIT_VAL;

        /*
         * Check first if the option is disabled.
         */
        snprintf(no_option, sizeof(no_option), "NO_%s", option);

        opt = strstr(options, no_option);
        if (opt)
                goto out_free;

        /*
         * Now that it is not disabled, if the string is there, it is
         * enabled. If the string is not there, the option does not exist.
         */
        opt = strstr(options, option);
        if (opt)
                retval = 1;
        else
                retval = OSNOISE_OPTION_INIT_VAL;

out_free:
        free(options);
        return retval;
}

static int osnoise_options_set_option(char *option, bool onoff)
{
        char no_option[128];

        if (onoff)
                return tracefs_instance_file_write(NULL, "osnoise/options", option);

        snprintf(no_option, sizeof(no_option), "NO_%s", option);

        return tracefs_instance_file_write(NULL, "osnoise/options", no_option);
}

static int osnoise_get_irq_disable(struct osnoise_context *context)
{
        if (context->opt_irq_disable != OSNOISE_OPTION_INIT_VAL)
                return context->opt_irq_disable;

        if (context->orig_opt_irq_disable != OSNOISE_OPTION_INIT_VAL)
                return context->orig_opt_irq_disable;

        context->orig_opt_irq_disable = osnoise_options_get_option("OSNOISE_IRQ_DISABLE");

        return context->orig_opt_irq_disable;
}

int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff)
{
        int opt_irq_disable = osnoise_get_irq_disable(context);
        int retval;

        if (opt_irq_disable == OSNOISE_OPTION_INIT_VAL)
                return -1;

        if (opt_irq_disable == onoff)
                return 0;

        retval = osnoise_options_set_option("OSNOISE_IRQ_DISABLE", onoff);
        if (retval < 0)
                return -1;

        context->opt_irq_disable = onoff;

        return 0;
}

static void osnoise_restore_irq_disable(struct osnoise_context *context)
{
        int retval;

        if (context->orig_opt_irq_disable == OSNOISE_OPTION_INIT_VAL)
                return;

        if (context->orig_opt_irq_disable == context->opt_irq_disable)
                goto out_done;

        retval = osnoise_options_set_option("OSNOISE_IRQ_DISABLE", context->orig_opt_irq_disable);
        if (retval < 0)
                err_msg("Could not restore original OSNOISE_IRQ_DISABLE option\n");

out_done:
        context->orig_opt_irq_disable = OSNOISE_OPTION_INIT_VAL;
}

static void osnoise_put_irq_disable(struct osnoise_context *context)
{
        osnoise_restore_irq_disable(context);

        if (context->orig_opt_irq_disable == OSNOISE_OPTION_INIT_VAL)
                return;

        context->orig_opt_irq_disable = OSNOISE_OPTION_INIT_VAL;
}

static int osnoise_get_workload(struct osnoise_context *context)
{
        if (context->opt_workload != OSNOISE_OPTION_INIT_VAL)
                return context->opt_workload;

        if (context->orig_opt_workload != OSNOISE_OPTION_INIT_VAL)
                return context->orig_opt_workload;

        context->orig_opt_workload = osnoise_options_get_option("OSNOISE_WORKLOAD");

        return context->orig_opt_workload;
}

int osnoise_set_workload(struct osnoise_context *context, bool onoff)
{
        int opt_workload = osnoise_get_workload(context);
        int retval;

        if (opt_workload == OSNOISE_OPTION_INIT_VAL)
                return -1;

        if (opt_workload == onoff)
                return 0;

        retval = osnoise_options_set_option("OSNOISE_WORKLOAD", onoff);
        if (retval < 0)
                return -2;

        context->opt_workload = onoff;

        return 0;
}

static void osnoise_restore_workload(struct osnoise_context *context)
{
        int retval;

        if (context->orig_opt_workload == OSNOISE_OPTION_INIT_VAL)
                return;

        if (context->orig_opt_workload == context->opt_workload)
                goto out_done;

        retval = osnoise_options_set_option("OSNOISE_WORKLOAD", context->orig_opt_workload);
        if (retval < 0)
                err_msg("Could not restore original OSNOISE_WORKLOAD option\n");

out_done:
        context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL;
}

static void osnoise_put_workload(struct osnoise_context *context)
{
        osnoise_restore_workload(context);

        if (context->orig_opt_workload == OSNOISE_OPTION_INIT_VAL)
                return;

        context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL;
}

enum {
        FLAG_CONTEXT_NEWLY_CREATED      = (1 << 0),
        FLAG_CONTEXT_DELETED            = (1 << 1),
};

/*
 * osnoise_get_context - increase the usage of a context and return it
 */
int osnoise_get_context(struct osnoise_context *context)
{
        int ret;

        if (context->flags & FLAG_CONTEXT_DELETED) {
                ret = -1;
        } else {
                context->ref++;
                ret = 0;
        }

        return ret;
}

/*
 * osnoise_context_alloc - alloc an osnoise_context
 *
 * The osnoise context contains the information of the "osnoise/" configs.
 * It is used to set and restore the config.
 */
struct osnoise_context *osnoise_context_alloc(void)
{
        struct osnoise_context *context;

        context = calloc(1, sizeof(*context));
        if (!context)
                return NULL;

        context->orig_stop_us           = OSNOISE_OPTION_INIT_VAL;
        context->stop_us                = OSNOISE_OPTION_INIT_VAL;

        context->orig_stop_total_us     = OSNOISE_OPTION_INIT_VAL;
        context->stop_total_us          = OSNOISE_OPTION_INIT_VAL;

        context->orig_print_stack       = OSNOISE_OPTION_INIT_VAL;
        context->print_stack            = OSNOISE_OPTION_INIT_VAL;

        context->orig_tracing_thresh    = OSNOISE_OPTION_INIT_VAL;
        context->tracing_thresh         = OSNOISE_OPTION_INIT_VAL;

        context->orig_opt_irq_disable   = OSNOISE_OPTION_INIT_VAL;
        context->opt_irq_disable        = OSNOISE_OPTION_INIT_VAL;

        context->orig_opt_workload      = OSNOISE_OPTION_INIT_VAL;
        context->opt_workload           = OSNOISE_OPTION_INIT_VAL;

        osnoise_get_context(context);

        return context;
}

/*
 * osnoise_put_context - put the osnoise_put_context
 *
 * If there is no other user for the context, the original data
 * is restored.
 */
void osnoise_put_context(struct osnoise_context *context)
{
        if (--context->ref < 1)
                context->flags |= FLAG_CONTEXT_DELETED;

        if (!(context->flags & FLAG_CONTEXT_DELETED))
                return;

        osnoise_put_cpus(context);
        osnoise_put_runtime_period(context);
        osnoise_put_stop_us(context);
        osnoise_put_stop_total_us(context);
        osnoise_put_timerlat_period_us(context);
        osnoise_put_print_stack(context);
        osnoise_put_tracing_thresh(context);
        osnoise_put_irq_disable(context);
        osnoise_put_workload(context);

        free(context);
}

/*
 * osnoise_destroy_tool - disable trace, restore configs and free data
 */
void osnoise_destroy_tool(struct osnoise_tool *top)
{
        if (!top)
                return;

        trace_instance_destroy(&top->trace);

        if (top->context)
                osnoise_put_context(top->context);

        free(top);
}

/*
 * osnoise_init_tool - init an osnoise tool
 *
 * It allocs data, create a context to store data and
 * creates a new trace instance for the tool.
 */
struct osnoise_tool *osnoise_init_tool(char *tool_name)
{
        struct osnoise_tool *top;
        int retval;

        top = calloc(1, sizeof(*top));
        if (!top)
                return NULL;

        top->context = osnoise_context_alloc();
        if (!top->context)
                goto out_err;

        retval = trace_instance_init(&top->trace, tool_name);
        if (retval)
                goto out_err;

        return top;
out_err:
        osnoise_destroy_tool(top);
        return NULL;
}

/*
 * osnoise_init_trace_tool - init a tracer instance to trace osnoise events
 */
struct osnoise_tool *osnoise_init_trace_tool(const char *tracer)
{
        struct osnoise_tool *trace;
        int retval;

        trace = osnoise_init_tool("osnoise_trace");
        if (!trace)
                return NULL;

        retval = tracefs_event_enable(trace->trace.inst, "osnoise", NULL);
        if (retval < 0 && !errno) {
                err_msg("Could not find osnoise events\n");
                goto out_err;
        }

        retval = enable_tracer_by_name(trace->trace.inst, tracer);
        if (retval) {
                err_msg("Could not enable %s tracer for tracing\n", tracer);
                goto out_err;
        }

        return trace;
out_err:
        osnoise_destroy_tool(trace);
        return NULL;
}

bool osnoise_trace_is_off(struct osnoise_tool *tool, struct osnoise_tool *record)
{
        /*
         * The tool instance is always present, it is the one used to collect
         * data.
         */
        if (!tracefs_trace_is_on(tool->trace.inst))
                return true;

        /*
         * The trace record instance is only enabled when -t is set. IOW, when the system
         * is tracing.
         */
        return record && !tracefs_trace_is_on(record->trace.inst);
}

/*
 * osnoise_report_missed_events - report number of events dropped by trace
 * buffer
 */
void
osnoise_report_missed_events(struct osnoise_tool *tool)
{
        unsigned long long total_events;

        if (tool->trace.missed_events == UINT64_MAX)
                printf("unknown number of events missed, results might not be accurate\n");
        else if (tool->trace.missed_events > 0) {
                total_events = tool->trace.processed_events + tool->trace.missed_events;

                printf("%lld (%.2f%%) events missed, results might not be accurate\n",
                       tool->trace.missed_events,
                       (double) tool->trace.missed_events / total_events * 100.0);
        }
}

/*
 * osnoise_apply_config - apply osnoise configs to the initialized tool
 */
int
osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params)
{
        int retval;

        params->common.kernel_workload = true;

        if (params->runtime || params->period) {
                retval = osnoise_set_runtime_period(tool->context,
                                                    params->runtime,
                                                    params->period);
        } else {
                retval = osnoise_set_runtime_period(tool->context,
                                                    DEFAULT_SAMPLE_PERIOD,
                                                    DEFAULT_SAMPLE_RUNTIME);
        }

        if (retval) {
                err_msg("Failed to set runtime and/or period\n");
                goto out_err;
        }

        retval = osnoise_set_tracing_thresh(tool->context, params->threshold);
        if (retval) {
                err_msg("Failed to set tracing_thresh\n");
                goto out_err;
        }

        return common_apply_config(tool, &params->common);

out_err:
        return -1;
}

int osnoise_enable(struct osnoise_tool *tool)
{
        struct osnoise_params *params = to_osnoise_params(tool->params);
        int retval;

        /*
         * Start the tracer here, after having set all instances.
         *
         * Let the trace instance start first for the case of hitting a stop
         * tracing while enabling other instances. The trace instance is the
         * one with most valuable information.
         */
        if (tool->record)
                trace_instance_start(&tool->record->trace);
        trace_instance_start(&tool->trace);

        if (params->common.warmup > 0) {
                debug_msg("Warming up for %d seconds\n", params->common.warmup);
                sleep(params->common.warmup);
                if (stop_tracing)
                        return -1;

                /*
                 * Clean up the buffer. The osnoise workload do not run
                 * with tracing off to avoid creating a performance penalty
                 * when not needed.
                 */
                retval = tracefs_instance_file_write(tool->trace.inst, "trace", "");
                if (retval < 0) {
                        debug_msg("Error cleaning up the buffer");
                        return retval;
                }
        }

        retval = osn_set_stop(tool);
        if (retval)
                return retval;

        return 0;
}

static void osnoise_usage(int err)
{
        int i;

        static const char *msg[] = {
                "",
                "osnoise version " VERSION,
                "",
                "  usage: [rtla] osnoise [MODE] ...",
                "",
                "  modes:",
                "     top   - prints the summary from osnoise tracer",
                "     hist  - prints a histogram of osnoise samples",
                "",
                "if no MODE is given, the top mode is called, passing the arguments",
                NULL,
        };

        for (i = 0; msg[i]; i++)
                fprintf(stderr, "%s\n", msg[i]);
        exit(err);
}

int osnoise_main(int argc, char *argv[])
{
        if (argc == 0)
                goto usage;

        /*
         * if osnoise was called without any argument, run the
         * default cmdline.
         */
        if (argc == 1) {
                run_tool(&osnoise_top_ops, argc, argv);
                exit(0);
        }

        if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) {
                osnoise_usage(0);
        } else if (strncmp(argv[1], "-", 1) == 0) {
                /* the user skipped the tool, call the default one */
                run_tool(&osnoise_top_ops, argc, argv);
                exit(0);
        } else if (strcmp(argv[1], "top") == 0) {
                run_tool(&osnoise_top_ops, argc-1, &argv[1]);
                exit(0);
        } else if (strcmp(argv[1], "hist") == 0) {
                run_tool(&osnoise_hist_ops, argc-1, &argv[1]);
                exit(0);
        }

usage:
        osnoise_usage(1);
        exit(1);
}

int hwnoise_main(int argc, char *argv[])
{
        run_tool(&osnoise_top_ops, argc, argv);
        exit(0);
}