root/sys/dev/drm/amd/display/modules/stats/stats.c
/*
 * Copyright 2016 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: AMD
 *
 */

#include "mod_stats.h"
#include "dm_services.h"
#include "dc.h"
#include "core_types.h"

#define DAL_STATS_ENABLE_REGKEY                 "DalStatsEnable"
#define DAL_STATS_ENABLE_REGKEY_DEFAULT         0x00000000
#define DAL_STATS_ENABLE_REGKEY_ENABLED         0x00000001

#define DAL_STATS_ENTRIES_REGKEY                "DalStatsEntries"
#define DAL_STATS_ENTRIES_REGKEY_DEFAULT        0x00350000
#define DAL_STATS_ENTRIES_REGKEY_MAX            0x01000000

#define DAL_STATS_EVENT_ENTRIES_DEFAULT         0x00000100

#define MOD_STATS_NUM_VSYNCS                    5
#define MOD_STATS_EVENT_STRING_MAX              512

struct stats_time_cache {
        unsigned int entry_id;

        unsigned long flip_timestamp_in_ns;
        unsigned long vupdate_timestamp_in_ns;

        unsigned int render_time_in_us;
        unsigned int avg_render_time_in_us_last_ten;
        unsigned int v_sync_time_in_us[MOD_STATS_NUM_VSYNCS];
        unsigned int num_vsync_between_flips;

        unsigned int flip_to_vsync_time_in_us;
        unsigned int vsync_to_flip_time_in_us;

        unsigned int min_window;
        unsigned int max_window;
        unsigned int v_total_min;
        unsigned int v_total_max;
        unsigned int event_triggers;

        unsigned int lfc_mid_point_in_us;
        unsigned int num_frames_inserted;
        unsigned int inserted_duration_in_us;

        unsigned int flags;
};

struct stats_event_cache {
        unsigned int entry_id;
        char event_string[MOD_STATS_EVENT_STRING_MAX];
};

struct core_stats {
        struct mod_stats public;
        struct dc *dc;

        bool enabled;
        unsigned int entries;
        unsigned int event_entries;
        unsigned int entry_id;

        struct stats_time_cache *time;
        unsigned int index;

        struct stats_event_cache *events;
        unsigned int event_index;

};

#define MOD_STATS_TO_CORE(mod_stats)\
                container_of(mod_stats, struct core_stats, public)

bool mod_stats_init(struct mod_stats *mod_stats)
{
        bool result = false;
        struct core_stats *core_stats = NULL;
        struct dc *dc = NULL;

        if (mod_stats == NULL)
                return false;

        core_stats = MOD_STATS_TO_CORE(mod_stats);
        dc = core_stats->dc;

        return result;
}

struct mod_stats *mod_stats_create(struct dc *dc)
{
        struct core_stats *core_stats = NULL;
        struct persistent_data_flag flag;
        unsigned int reg_data;
        int i = 0;

        if (dc == NULL)
                goto fail_construct;

        core_stats = kzalloc(sizeof(struct core_stats), GFP_KERNEL);

        if (core_stats == NULL)
                goto fail_construct;

        core_stats->dc = dc;

        core_stats->enabled = DAL_STATS_ENABLE_REGKEY_DEFAULT;
        if (dm_read_persistent_data(dc->ctx, NULL, NULL,
                        DAL_STATS_ENABLE_REGKEY,
                        &reg_data, sizeof(unsigned int), &flag))
                core_stats->enabled = reg_data;

        if (core_stats->enabled) {
                core_stats->entries = DAL_STATS_ENTRIES_REGKEY_DEFAULT;
                if (dm_read_persistent_data(dc->ctx, NULL, NULL,
                                DAL_STATS_ENTRIES_REGKEY,
                                &reg_data, sizeof(unsigned int), &flag)) {
                        if (reg_data > DAL_STATS_ENTRIES_REGKEY_MAX)
                                core_stats->entries = DAL_STATS_ENTRIES_REGKEY_MAX;
                        else
                                core_stats->entries = reg_data;
                }
                core_stats->time = kcalloc(core_stats->entries,
                                                sizeof(struct stats_time_cache),
                                                GFP_KERNEL);

                if (core_stats->time == NULL)
                        goto fail_construct_time;

                core_stats->event_entries = DAL_STATS_EVENT_ENTRIES_DEFAULT;
                core_stats->events = kcalloc(core_stats->event_entries,
                                             sizeof(struct stats_event_cache),
                                             GFP_KERNEL);

                if (core_stats->events == NULL)
                        goto fail_construct_events;

        } else {
                core_stats->entries = 0;
        }

        /* Purposely leave index 0 unused so we don't need special logic to
         * handle calculation cases that depend on previous flip data.
         */
        core_stats->index = 1;
        core_stats->event_index = 0;

        // Keeps track of ordering within the different stats structures
        core_stats->entry_id = 0;

        return &core_stats->public;

fail_construct_events:
        kfree(core_stats->time);

fail_construct_time:
        kfree(core_stats);

fail_construct:
        return NULL;
}

void mod_stats_destroy(struct mod_stats *mod_stats)
{
        if (mod_stats != NULL) {
                struct core_stats *core_stats = MOD_STATS_TO_CORE(mod_stats);

                kfree(core_stats->time);
                kfree(core_stats->events);
                kfree(core_stats);
        }
}

void mod_stats_dump(struct mod_stats *mod_stats)
{
        struct dc  *dc = NULL;
        struct dal_logger *logger = NULL;
        struct core_stats *core_stats = NULL;
        struct stats_time_cache *time = NULL;
        struct stats_event_cache *events = NULL;
        unsigned int time_index = 1;
        unsigned int event_index = 0;
        unsigned int index = 0;
        struct log_entry log_entry;

        if (mod_stats == NULL)
                return;

        core_stats = MOD_STATS_TO_CORE(mod_stats);
        dc = core_stats->dc;
        logger = dc->ctx->logger;
        time = core_stats->time;
        events = core_stats->events;

        DISPLAY_STATS_BEGIN(log_entry);

        DISPLAY_STATS("==Display Caps==\n");

        DISPLAY_STATS("==Display Stats==\n");

        DISPLAY_STATS("%10s %10s %10s %10s %10s"
                        " %11s %11s %17s %10s %14s"
                        " %10s %10s %10s %10s %10s"
                        " %10s %10s %10s %10s\n",
                "render", "avgRender",
                "minWindow", "midPoint", "maxWindow",
                "vsyncToFlip", "flipToVsync", "vsyncsBetweenFlip",
                "numFrame", "insertDuration",
                "vTotalMin", "vTotalMax", "eventTrigs",
                "vSyncTime1", "vSyncTime2", "vSyncTime3",
                "vSyncTime4", "vSyncTime5", "flags");

        for (int i = 0; i < core_stats->entry_id; i++) {
                if (event_index < core_stats->event_index &&
                                i == events[event_index].entry_id) {
                        DISPLAY_STATS("==Event==%s\n", events[event_index].event_string);
                        event_index++;
                } else if (time_index < core_stats->index &&
                                i == time[time_index].entry_id) {
                        DISPLAY_STATS("%10u %10u %10u %10u %10u"
                                        " %11u %11u %17u %10u %14u"
                                        " %10u %10u %10u %10u %10u"
                                        " %10u %10u %10u %10u\n",
                                time[time_index].render_time_in_us,
                                time[time_index].avg_render_time_in_us_last_ten,
                                time[time_index].min_window,
                                time[time_index].lfc_mid_point_in_us,
                                time[time_index].max_window,
                                time[time_index].vsync_to_flip_time_in_us,
                                time[time_index].flip_to_vsync_time_in_us,
                                time[time_index].num_vsync_between_flips,
                                time[time_index].num_frames_inserted,
                                time[time_index].inserted_duration_in_us,
                                time[time_index].v_total_min,
                                time[time_index].v_total_max,
                                time[time_index].event_triggers,
                                time[time_index].v_sync_time_in_us[0],
                                time[time_index].v_sync_time_in_us[1],
                                time[time_index].v_sync_time_in_us[2],
                                time[time_index].v_sync_time_in_us[3],
                                time[time_index].v_sync_time_in_us[4],
                                time[time_index].flags);

                        time_index++;
                }
        }

        DISPLAY_STATS_END(log_entry);
}

void mod_stats_reset_data(struct mod_stats *mod_stats)
{
        struct core_stats *core_stats = NULL;
        struct stats_time_cache *time = NULL;
        unsigned int index = 0;

        if (mod_stats == NULL)
                return;

        core_stats = MOD_STATS_TO_CORE(mod_stats);

        memset(core_stats->time, 0,
                sizeof(struct stats_time_cache) * core_stats->entries);

        memset(core_stats->events, 0,
                sizeof(struct stats_event_cache) * core_stats->event_entries);

        core_stats->index = 1;
        core_stats->event_index = 0;

        // Keeps track of ordering within the different stats structures
        core_stats->entry_id = 0;
}

void mod_stats_update_event(struct mod_stats *mod_stats,
                char *event_string,
                unsigned int length)
{
        struct core_stats *core_stats = NULL;
        struct stats_event_cache *events = NULL;
        unsigned int index = 0;
        unsigned int copy_length = 0;

        if (mod_stats == NULL)
                return;

        core_stats = MOD_STATS_TO_CORE(mod_stats);

        if (core_stats->event_index >= core_stats->event_entries)
                return;

        events = core_stats->events;
        index = core_stats->event_index;

        copy_length = length;
        if (length > MOD_STATS_EVENT_STRING_MAX)
                copy_length = MOD_STATS_EVENT_STRING_MAX;

        memcpy(&events[index].event_string, event_string, copy_length);
        events[index].event_string[copy_length - 1] = '\0';

        events[index].entry_id = core_stats->entry_id;
        core_stats->event_index++;
        core_stats->entry_id++;
}

void mod_stats_update_flip(struct mod_stats *mod_stats,
                unsigned long timestamp_in_ns)
{
        struct core_stats *core_stats = NULL;
        struct stats_time_cache *time = NULL;
        unsigned int index = 0;

        if (mod_stats == NULL)
                return;

        core_stats = MOD_STATS_TO_CORE(mod_stats);

        if (core_stats->index >= core_stats->entries)
                return;

        time = core_stats->time;
        index = core_stats->index;

        time[index].flip_timestamp_in_ns = timestamp_in_ns;
        time[index].render_time_in_us =
                (timestamp_in_ns - time[index - 1].flip_timestamp_in_ns) / 1000;

        if (index >= 10) {
                for (unsigned int i = 0; i < 10; i++)
                        time[index].avg_render_time_in_us_last_ten +=
                                        time[index - i].render_time_in_us;
                time[index].avg_render_time_in_us_last_ten /= 10;
        }

        if (time[index].num_vsync_between_flips > 0)
                time[index].vsync_to_flip_time_in_us =
                        (timestamp_in_ns -
                                time[index].vupdate_timestamp_in_ns) / 1000;
        else
                time[index].vsync_to_flip_time_in_us =
                        (timestamp_in_ns -
                                time[index - 1].vupdate_timestamp_in_ns) / 1000;

        time[index].entry_id = core_stats->entry_id;
        core_stats->index++;
        core_stats->entry_id++;
}

void mod_stats_update_vupdate(struct mod_stats *mod_stats,
                unsigned long timestamp_in_ns)
{
        struct core_stats *core_stats = NULL;
        struct stats_time_cache *time = NULL;
        unsigned int index = 0;
        unsigned int num_vsyncs = 0;
        unsigned int prev_vsync_in_ns = 0;

        if (mod_stats == NULL)
                return;

        core_stats = MOD_STATS_TO_CORE(mod_stats);

        if (core_stats->index >= core_stats->entries)
                return;

        time = core_stats->time;
        index = core_stats->index;
        num_vsyncs = time[index].num_vsync_between_flips;

        if (num_vsyncs < MOD_STATS_NUM_VSYNCS) {
                if (num_vsyncs == 0) {
                        prev_vsync_in_ns =
                                time[index - 1].vupdate_timestamp_in_ns;

                        time[index].flip_to_vsync_time_in_us =
                                (timestamp_in_ns -
                                        time[index - 1].flip_timestamp_in_ns) /
                                        1000;
                } else {
                        prev_vsync_in_ns =
                                time[index].vupdate_timestamp_in_ns;
                }

                time[index].v_sync_time_in_us[num_vsyncs] =
                        (timestamp_in_ns - prev_vsync_in_ns) / 1000;
        }

        time[index].vupdate_timestamp_in_ns = timestamp_in_ns;
        time[index].num_vsync_between_flips++;
}

void mod_stats_update_freesync(struct mod_stats *mod_stats,
                unsigned int v_total_min,
                unsigned int v_total_max,
                unsigned int event_triggers,
                unsigned int window_min,
                unsigned int window_max,
                unsigned int lfc_mid_point_in_us,
                unsigned int inserted_frames,
                unsigned int inserted_duration_in_us)
{
        struct core_stats *core_stats = NULL;
        struct stats_time_cache *time = NULL;
        unsigned int index = 0;

        if (mod_stats == NULL)
                return;

        core_stats = MOD_STATS_TO_CORE(mod_stats);

        if (core_stats->index >= core_stats->entries)
                return;

        time = core_stats->time;
        index = core_stats->index;

        time[index].v_total_min = v_total_min;
        time[index].v_total_max = v_total_max;
        time[index].event_triggers = event_triggers;
        time[index].min_window = window_min;
        time[index].max_window = window_max;
        time[index].lfc_mid_point_in_us = lfc_mid_point_in_us;
        time[index].num_frames_inserted = inserted_frames;
        time[index].inserted_duration_in_us = inserted_duration_in_us;
}