/*
 * BSD LICENSE
 *
 * Copyright(c) 2014-2020 Intel Corporation. All rights reserved.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of Intel Corporation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @brief Set of utility functions to operate on Platform QoS (pqos) data
 *        structures.
 *
 * These functions need no synchronization mechanisms.
 *
 */
#include <stdlib.h>
#include <string.h>

#include "pqos.h"
#include "types.h"
#include "utils.h"
#include "cpuinfo.h"

#define TOPO_OBJ_SOCKET     0
#define TOPO_OBJ_L2_CLUSTER 2
#define TOPO_OBJ_L3_CLUSTER 3

static int m_interface = PQOS_INTER_MSR;

int
_pqos_utils_init(int interface)
{
        if (interface == PQOS_INTER_OS_RESCTRL_MON)
                m_interface = PQOS_INTER_OS;
        else
                m_interface = interface;

        return PQOS_RETVAL_OK;
}

unsigned *
pqos_cpu_get_mba_ids(const struct pqos_cpuinfo *cpu, unsigned *count)
{
        unsigned mba_id_count = 0, i = 0;
        unsigned *mba_ids = NULL;

        ASSERT(cpu != NULL);
        ASSERT(count != NULL);
        if (cpu == NULL || count == NULL)
                return NULL;

        mba_ids = (unsigned *)malloc(sizeof(mba_ids[0]) * cpu->num_cores);
        if (mba_ids == NULL)
                return NULL;

        for (i = 0; i < cpu->num_cores; i++) {
                unsigned j = 0;

                /**
                 * Check if this mba id is already on the \a mbas list
                 */
                for (j = 0; j < mba_id_count && mba_id_count > 0; j++)
                        if (cpu->cores[i].mba_id == mba_ids[j])
                                break;

                if (j >= mba_id_count || mba_id_count == 0) {
                        /**
                         * This mba_id wasn't reported before
                         */
                        mba_ids[mba_id_count++] = cpu->cores[i].mba_id;
                }
        }

        *count = mba_id_count;
        return mba_ids;
}

unsigned *
pqos_cpu_get_l3cat_ids(const struct pqos_cpuinfo *cpu, unsigned *count)
{
        unsigned l3cat_count = 0, i = 0;
        unsigned *l3cat_ids = NULL;

        ASSERT(cpu != NULL);
        ASSERT(count != NULL);
        if (cpu == NULL || count == NULL)
                return NULL;

        l3cat_ids = (unsigned *)malloc(sizeof(l3cat_ids[0]) * cpu->num_cores);
        if (l3cat_ids == NULL)
                return NULL;

        for (i = 0; i < cpu->num_cores; i++) {
                unsigned j = 0;

                /**
                 * Check if this l3cat id is already on the \a l3cat_ids list
                 */
                for (j = 0; j < l3cat_count && l3cat_count > 0; j++)
                        if (cpu->cores[i].l3cat_id == l3cat_ids[j])
                                break;

                if (j >= l3cat_count || l3cat_count == 0) {
                        /**
                         * This l3cat_id wasn't reported before
                         */
                        l3cat_ids[l3cat_count++] = cpu->cores[i].l3cat_id;
                }
        }

        *count = l3cat_count;
        return l3cat_ids;
}

unsigned *
pqos_cpu_get_sockets(const struct pqos_cpuinfo *cpu, unsigned *count)
{
        unsigned scount = 0, i = 0;
        unsigned *sockets = NULL;

        ASSERT(cpu != NULL);
        ASSERT(count != NULL);
        if (cpu == NULL || count == NULL)
                return NULL;

        sockets = (unsigned *)malloc(sizeof(sockets[0]) * cpu->num_cores);
        if (sockets == NULL)
                return NULL;

        for (i = 0; i < cpu->num_cores; i++) {
                unsigned j = 0;

                /**
                 * Check if this socket id is already on the \a sockets list
                 */
                for (j = 0; j < scount && scount > 0; j++)
                        if (cpu->cores[i].socket == sockets[j])
                                break;

                if (j >= scount || scount == 0) {
                        /**
                         * This socket wasn't reported before
                         */
                        sockets[scount++] = cpu->cores[i].socket;
                }
        }

        *count = scount;
        return sockets;
}

unsigned *
pqos_cpu_get_l2ids(const struct pqos_cpuinfo *cpu, unsigned *count)
{
        unsigned l2count = 0, i = 0;
        unsigned *l2ids = NULL;

        ASSERT(cpu != NULL);
        ASSERT(count != NULL);
        if (cpu == NULL || count == NULL)
                return NULL;

        l2ids = (unsigned *)malloc(sizeof(l2ids[0]) * cpu->num_cores);
        if (l2ids == NULL)
                return NULL;

        for (i = 0; i < cpu->num_cores; i++) {
                unsigned j = 0;

                /**
                 * Check if this L2 id is already on the list
                 */
                for (j = 0; j < l2count && l2count > 0; j++)
                        if (cpu->cores[i].l2_id == l2ids[j])
                                break;

                if (j >= l2count || l2count == 0) {
                        /**
                         * This l2id wasn't reported before
                         */
                        l2ids[l2count++] = cpu->cores[i].l2_id;
                }
        }

        *count = l2count;
        return l2ids;
}

/**
 * @brief Creates list of cores belonging to given topology object
 *
 * @param [in] cpu CPU topology
 * @param [in] type CPU topology object type to search cores for
 *             TOPO_OBJ_SOCKET - sockets
 *             TOPO_OBJ_L2_CLUSTER - L2 cache clusters
 *             TOPO_OBJ_L3_CLUSTER - L3 cache clusters
 * @param [in] id CPU topology object ID to search cores for
 * @param [out] count place to put number of objects found
 *
 * @return Pointer to list of cores for given topology object
 * @retval NULL on error or if no core found
 */
static unsigned *
__get_cores_per_topology_obj(const struct pqos_cpuinfo *cpu,
                             const int type,
                             const unsigned id,
                             unsigned *count)
{
        unsigned num = 0, i = 0;
        unsigned *core_list = NULL;

        ASSERT(cpu != NULL);
        ASSERT(count != NULL);
        if (cpu == NULL || count == NULL)
                return NULL;

        core_list = (unsigned *)malloc(cpu->num_cores * sizeof(core_list[0]));
        if (core_list == NULL)
                return NULL;

        for (i = 0; i < cpu->num_cores; i++)
                if ((type == TOPO_OBJ_L3_CLUSTER &&
                     id == cpu->cores[i].l3_id) ||
                    (type == TOPO_OBJ_L2_CLUSTER &&
                     id == cpu->cores[i].l2_id) ||
                    (type == TOPO_OBJ_SOCKET && id == cpu->cores[i].socket))
                        core_list[num++] = cpu->cores[i].lcore;

        if (num == 0) {
                free(core_list);
                return NULL;
        }

        *count = num;
        return core_list;
}

unsigned *
pqos_cpu_get_cores_l3id(const struct pqos_cpuinfo *cpu,
                        const unsigned l3_id,
                        unsigned *count)
{
        return __get_cores_per_topology_obj(cpu, TOPO_OBJ_L3_CLUSTER, l3_id,
                                            count);
}

unsigned *
pqos_cpu_get_cores(const struct pqos_cpuinfo *cpu,
                   const unsigned socket,
                   unsigned *count)
{
        unsigned i = 0, cnt = 0;
        unsigned *cores = NULL;

        ASSERT(cpu != NULL);
        ASSERT(count != NULL);

        if (cpu == NULL || count == NULL)
                return NULL;

        cores = (unsigned *)malloc(cpu->num_cores * sizeof(cores[0]));
        if (cores == NULL)
                return NULL;

        for (i = 0; i < cpu->num_cores; i++)
                if (cpu->cores[i].socket == socket)
                        cores[cnt++] = cpu->cores[i].lcore;

        if (!cnt) {
                free(cores);
                return NULL;
        }

        *count = cnt;
        return cores;
}

const struct pqos_coreinfo *
pqos_cpu_get_core_info(const struct pqos_cpuinfo *cpu, unsigned lcore)
{
        unsigned i;

        ASSERT(cpu != NULL);

        if (cpu == NULL)
                return NULL;

        for (i = 0; i < cpu->num_cores; i++)
                if (cpu->cores[i].lcore == lcore)
                        return &cpu->cores[i];

        return NULL;
}

int
pqos_cpu_get_one_core(const struct pqos_cpuinfo *cpu,
                      const unsigned socket,
                      unsigned *lcore)
{
        unsigned i = 0;

        ASSERT(cpu != NULL);
        ASSERT(lcore != NULL);

        if (cpu == NULL || lcore == NULL)
                return PQOS_RETVAL_PARAM;

        for (i = 0; i < cpu->num_cores; i++)
                if (cpu->cores[i].socket == socket) {
                        *lcore = cpu->cores[i].lcore;
                        return PQOS_RETVAL_OK;
                }

        return PQOS_RETVAL_ERROR;
}

int
pqos_cpu_get_one_by_l3cat_id(const struct pqos_cpuinfo *cpu,
                             const unsigned l3cat_id,
                             unsigned *lcore)
{
        unsigned i = 0;

        ASSERT(cpu != NULL);
        ASSERT(lcore != NULL);

        if (cpu == NULL || lcore == NULL)
                return PQOS_RETVAL_PARAM;

        for (i = 0; i < cpu->num_cores; i++)
                if (cpu->cores[i].l3cat_id == l3cat_id) {
                        *lcore = cpu->cores[i].lcore;
                        return PQOS_RETVAL_OK;
                }

        return PQOS_RETVAL_ERROR;
}

int
pqos_cpu_get_one_by_mba_id(const struct pqos_cpuinfo *cpu,
                           const unsigned mba_id,
                           unsigned *lcore)
{
        unsigned i = 0;

        ASSERT(cpu != NULL);
        ASSERT(lcore != NULL);

        if (cpu == NULL || lcore == NULL)
                return PQOS_RETVAL_PARAM;

        for (i = 0; i < cpu->num_cores; i++)
                if (cpu->cores[i].mba_id == mba_id) {
                        *lcore = cpu->cores[i].lcore;
                        return PQOS_RETVAL_OK;
                }

        return PQOS_RETVAL_ERROR;
}

int
pqos_cpu_get_one_by_l2id(const struct pqos_cpuinfo *cpu,
                         const unsigned l2id,
                         unsigned *lcore)
{
        unsigned i = 0;

        ASSERT(cpu != NULL);
        ASSERT(lcore != NULL);

        if (cpu == NULL || lcore == NULL)
                return PQOS_RETVAL_PARAM;

        for (i = 0; i < cpu->num_cores; i++)
                if (cpu->cores[i].l2_id == l2id) {
                        *lcore = cpu->cores[i].lcore;
                        return PQOS_RETVAL_OK;
                }

        return PQOS_RETVAL_ERROR;
}

int
pqos_cpu_check_core(const struct pqos_cpuinfo *cpu, const unsigned lcore)
{
        unsigned i = 0;

        ASSERT(cpu != NULL);
        if (cpu == NULL)
                return PQOS_RETVAL_PARAM;

        for (i = 0; i < cpu->num_cores; i++)
                if (cpu->cores[i].lcore == lcore)
                        return PQOS_RETVAL_OK;

        return PQOS_RETVAL_ERROR;
}

int
pqos_cpu_get_socketid(const struct pqos_cpuinfo *cpu,
                      const unsigned lcore,
                      unsigned *socket)
{
        unsigned i = 0;

        if (cpu == NULL || socket == NULL)
                return PQOS_RETVAL_PARAM;

        for (i = 0; i < cpu->num_cores; i++)
                if (cpu->cores[i].lcore == lcore) {
                        *socket = cpu->cores[i].socket;
                        return PQOS_RETVAL_OK;
                }

        return PQOS_RETVAL_ERROR;
}

int
pqos_cpu_get_clusterid(const struct pqos_cpuinfo *cpu,
                       const unsigned lcore,
                       unsigned *cluster)
{
        unsigned i = 0;

        if (cpu == NULL || cluster == NULL)
                return PQOS_RETVAL_PARAM;

        for (i = 0; i < cpu->num_cores; i++)
                if (cpu->cores[i].lcore == lcore) {
                        *cluster = cpu->cores[i].l3_id;
                        return PQOS_RETVAL_OK;
                }

        return PQOS_RETVAL_ERROR;
}

int
pqos_cap_get_type(const struct pqos_cap *cap,
                  const enum pqos_cap_type type,
                  const struct pqos_capability **cap_item)
{
        int ret = PQOS_RETVAL_RESOURCE;
        unsigned i;

        ASSERT(cap != NULL && cap_item != NULL);
        if (cap == NULL || cap_item == NULL)
                return PQOS_RETVAL_PARAM;

        *cap_item = NULL;

        ASSERT(type < PQOS_CAP_TYPE_NUMOF);
        if (type >= PQOS_CAP_TYPE_NUMOF)
                return PQOS_RETVAL_PARAM;

        for (i = 0; i < cap->num_cap; i++) {
                if (cap->capabilities[i].type != type)
                        continue;
                *cap_item = &cap->capabilities[i];
                ret = PQOS_RETVAL_OK;
                break;
        }

        return ret;
}

int
pqos_cap_get_event(const struct pqos_cap *cap,
                   const enum pqos_mon_event event,
                   const struct pqos_monitor **p_mon)
{
        const struct pqos_capability *cap_item = NULL;
        const struct pqos_cap_mon *mon = NULL;
        int ret = PQOS_RETVAL_OK;
        unsigned i;

        if (cap == NULL || p_mon == NULL)
                return PQOS_RETVAL_PARAM;

        ret = pqos_cap_get_type(cap, PQOS_CAP_TYPE_MON, &cap_item);
        if (ret != PQOS_RETVAL_OK)
                return ret;

        ASSERT(cap_item != NULL);
        mon = cap_item->u.mon;

        ret = PQOS_RETVAL_ERROR;

        for (i = 0; i < mon->num_events; i++) {
                if (mon->events[i].type != event)
                        continue;

                *p_mon = &mon->events[i];
                ret = PQOS_RETVAL_OK;
                break;
        }

        return ret;
}

int
pqos_l3ca_get_cos_num(const struct pqos_cap *cap, unsigned *cos_num)
{
        const struct pqos_capability *item = NULL;
        int ret = PQOS_RETVAL_OK;

        ASSERT(cap != NULL && cos_num != NULL);
        if (cap == NULL || cos_num == NULL)
                return PQOS_RETVAL_PARAM;

        ret = pqos_cap_get_type(cap, PQOS_CAP_TYPE_L3CA, &item);
        if (ret != PQOS_RETVAL_OK)
                return ret; /**< no L3CA capability */

        ASSERT(item != NULL);
        *cos_num = item->u.l3ca->num_classes;
        return ret;
}

int
pqos_l2ca_get_cos_num(const struct pqos_cap *cap, unsigned *cos_num)
{
        const struct pqos_capability *item = NULL;
        int ret = PQOS_RETVAL_OK;

        ASSERT(cap != NULL && cos_num != NULL);
        if (cap == NULL || cos_num == NULL)
                return PQOS_RETVAL_PARAM;

        ret = pqos_cap_get_type(cap, PQOS_CAP_TYPE_L2CA, &item);
        if (ret != PQOS_RETVAL_OK)
                return ret; /**< no L2CA capability */

        ASSERT(item != NULL);
        *cos_num = item->u.l2ca->num_classes;
        return ret;
}

int
pqos_mba_get_cos_num(const struct pqos_cap *cap, unsigned *cos_num)
{
        const struct pqos_capability *item = NULL;
        int ret = PQOS_RETVAL_OK;

        ASSERT(cap != NULL && cos_num != NULL);
        if (cap == NULL || cos_num == NULL)
                return PQOS_RETVAL_PARAM;

        ret = pqos_cap_get_type(cap, PQOS_CAP_TYPE_MBA, &item);
        if (ret != PQOS_RETVAL_OK)
                return ret; /**< no MBA capability */

        ASSERT(item != NULL);
        *cos_num = item->u.mba->num_classes;
        return ret;
}

int
pqos_l3ca_cdp_enabled(const struct pqos_cap *cap,
                      int *cdp_supported,
                      int *cdp_enabled)
{
        const struct pqos_capability *item = NULL;
        int ret = PQOS_RETVAL_OK;

        ASSERT(cap != NULL && (cdp_enabled != NULL || cdp_supported != NULL));
        if (cap == NULL || (cdp_enabled == NULL && cdp_supported == NULL))
                return PQOS_RETVAL_PARAM;

        ret = pqos_cap_get_type(cap, PQOS_CAP_TYPE_L3CA, &item);
        if (ret != PQOS_RETVAL_OK)
                return ret; /**< no L3CA capability */

        ASSERT(item != NULL);
        if (cdp_supported != NULL)
                *cdp_supported = item->u.l3ca->cdp;

        if (cdp_enabled != NULL)
                *cdp_enabled = item->u.l3ca->cdp_on;

        return ret;
}

int
pqos_l2ca_cdp_enabled(const struct pqos_cap *cap,
                      int *cdp_supported,
                      int *cdp_enabled)
{
        const struct pqos_capability *l2ca = NULL;
        int ret = PQOS_RETVAL_OK;

        ASSERT(cap != NULL && (cdp_enabled != NULL || cdp_supported != NULL));
        if (cap == NULL || (cdp_enabled == NULL && cdp_supported == NULL))
                return PQOS_RETVAL_PARAM;

        ret = pqos_cap_get_type(cap, PQOS_CAP_TYPE_L2CA, &l2ca);
        if (ret != PQOS_RETVAL_OK)
                return ret; /**< no L2CA capability */

        ASSERT(l2ca != NULL);
        if (cdp_supported != NULL)
                *cdp_supported = l2ca->u.l2ca->cdp;

        if (cdp_enabled != NULL)
                *cdp_enabled = l2ca->u.l2ca->cdp_on;

        return ret;
}

int
pqos_mba_ctrl_enabled(const struct pqos_cap *cap,
                      int *ctrl_supported,
                      int *ctrl_enabled)
{
        const struct pqos_capability *mba_cap = NULL;
        int ret = PQOS_RETVAL_OK;

        ASSERT(cap != NULL && (ctrl_supported != NULL || ctrl_enabled != NULL));
        if (cap == NULL || (ctrl_supported == NULL && ctrl_enabled == NULL))
                return PQOS_RETVAL_PARAM;

        ret = pqos_cap_get_type(cap, PQOS_CAP_TYPE_MBA, &mba_cap);
        if (ret != PQOS_RETVAL_OK)
                return ret; /**< no MBA capability */

        ASSERT(mba_cap != NULL);
        if (ctrl_supported != NULL)
                *ctrl_supported = mba_cap->u.mba->ctrl;

        if (ctrl_enabled != NULL)
                *ctrl_enabled = mba_cap->u.mba->ctrl_on;

        return PQOS_RETVAL_OK;
}

enum pqos_vendor
pqos_get_vendor(const struct pqos_cpuinfo *m_cpu)
{
        return m_cpu->vendor;
}
