/*
 * BSD LICENSE
 *
 * Copyright(c) 2014-2022 Intel Corporation. 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 Host implementation of PQoS API / capabilities.
 *
 * This module is responsible for PQoS management and capability
 * functionalities.
 *
 * Management functions include:
 * - management includes initializing and shutting down all other sub-modules
 *   including: monitoring, allocation, log, cpuinfo and machine
 * - provide functions for safe access to PQoS API - this is required for
 *   allocation and monitoring modules which also implement PQoS API
 *
 * Capability functions:
 * - monitoring detection, this is to discover all monitoring event types.
 * - LLC allocation detection, this is to discover last level cache
 *   allocation feature.
 * - A new targeted function has to be implemented to discover new allocation
 *   technology.
 */

#include "cap.h"

#include "allocation.h"
#include "api.h"
#include "cpuinfo.h"
#include "hw_cap.h"
#include "log.h"
#include "machine.h"
#include "monitoring.h"
#include "os_cap.h"
#include "resctrl.h"
#include "resctrl_alloc.h"
#include "utils.h"

#include <fcntl.h> /* O_CREAT */
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> /* S_Ixxx */
#include <unistd.h>   /* usleep(), lockf() */

/**
 * ---------------------------------------
 * Local macros
 * ---------------------------------------
 */

#ifndef LOCKFILE
#ifdef __linux__
#define LOCKFILE "/var/lock/libpqos"
#endif
#ifdef __FreeBSD__
#define LOCKFILE "/var/tmp/libpqos.lockfile"
#endif
#endif /*!LOCKFILE*/

#define PROC_CPUINFO "/proc/cpuinfo"

/**
 * ---------------------------------------
 * Local data types
 * ---------------------------------------
 */

/**
 * ---------------------------------------
 * Local data structures
 * ---------------------------------------
 */

/**
 * This pointer is allocated and initialized in this module.
 * Then other sub-modules get this pointer in order to retrieve
 * capability information.
 */
static struct pqos_cap *m_cap = NULL;

/**
 * This gets allocated and initialized in this module.
 * This hold information about CPU topology in PQoS format.
 */
static const struct pqos_cpuinfo *m_cpu = NULL;

/**
 * Library initialization status.
 */
static int m_init_done = 0;

/**
 * API thread/process safe access is secured through these locks.
 */
static int m_apilock = -1;
static pthread_mutex_t m_apilock_mutex;

/**
 * Interface status
 *   0  PQOS_INTER_MSR
 *   1  PQOS_INTER_OS
 *   2  PQOS_INTER_OS_RESCTRL_MON
 */
static enum pqos_interface m_interface = PQOS_INTER_MSR;
/**
 * ---------------------------------------
 * Functions for safe multi-threading
 * ---------------------------------------
 */

/**
 * @brief Initializes API locks
 *
 * @return Operation status
 * @retval 0 success
 * @retval -1 error
 */
static int
_pqos_api_init(void)
{

        const char *lock_filename = LOCKFILE;

        if (m_apilock != -1)
                return -1;

        m_apilock = open(lock_filename, O_WRONLY | O_CREAT,
                         S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
        if (m_apilock == -1)
                return -1;

        if (pthread_mutex_init(&m_apilock_mutex, NULL) != 0) {
                close(m_apilock);
                m_apilock = -1;
                return -1;
        }

        return 0;
}

/**
 * @brief Uninitializes API locks
 *
 * @return Operation status
 * @retval 0 success
 * @retval -1 error
 */
static int
_pqos_api_exit(void)
{
        int ret = 0;

        if (close(m_apilock) != 0)
                ret = -1;

        if (pthread_mutex_destroy(&m_apilock_mutex) != 0)
                ret = -1;

        m_apilock = -1;

        return ret;
}

void
_pqos_api_lock(void)
{
        int err = 0;

        if (lockf(m_apilock, F_LOCK, 0) != 0)
                err = 1;

        if (pthread_mutex_lock(&m_apilock_mutex) != 0)
                err = 1;

        if (err)
                LOG_ERROR("API lock error!\n");
}

void
_pqos_api_unlock(void)
{
        int err = 0;

        if (lockf(m_apilock, F_ULOCK, 0) != 0)
                err = 1;

        if (pthread_mutex_unlock(&m_apilock_mutex) != 0)
                err = 1;

        if (err)
                LOG_ERROR("API unlock error!\n");
}

/**
 * ---------------------------------------
 * Function for library initialization
 * ---------------------------------------
 */

int
_pqos_check_init(const int expect)
{
        if (m_init_done && (!expect)) {
                LOG_ERROR("PQoS library already initialized\n");
                return PQOS_RETVAL_INIT;
        }

        if ((!m_init_done) && expect) {
                LOG_ERROR("PQoS library not initialized\n");
                return PQOS_RETVAL_INIT;
        }

        return PQOS_RETVAL_OK;
}

/**
 * @brief Discovers support of L3 CAT
 *
 * @param[out] r_cap place to store L3 CAT capabilities structure
 * @param[in] cpu detected cpu topology
 * @param[in] iface Selected interface
 *
 * @return Operation status
 * @retval PQOS_RETVAL_OK success
 */
static int
cap_l3ca_discover(struct pqos_cap_l3ca **r_cap,
                  const struct pqos_cpuinfo *cpu,
                  const enum pqos_interface iface)
{
        struct pqos_cap_l3ca *cap = NULL;
        int ret;

        cap = (struct pqos_cap_l3ca *)malloc(sizeof(*cap));
        if (cap == NULL)
                return PQOS_RETVAL_RESOURCE;

        switch (iface) {
        case PQOS_INTER_MSR:
                ret = hw_cap_l3ca_discover(cap, cpu);
                break;
#ifdef __linux__
        case PQOS_INTER_OS:
        case PQOS_INTER_OS_RESCTRL_MON:
                ret = os_cap_l3ca_discover(cap, cpu);
                break;
#endif
        default:
                ret = PQOS_RETVAL_RESOURCE;
                break;
        }

        if (ret == PQOS_RETVAL_OK)
                *r_cap = cap;
        else
                free(cap);

        return ret;
}

/**
 * @brief Discovers support of L2 CAT
 *
 * @param[out] r_cap place to store L2 CAT capabilities structure
 * @param[in] cpu detected cpu topology
 * @param[in] iface Selected interface
 *
 * @return Operation status
 * @retval PQOS_RETVAL_OK success
 */
static int
cap_l2ca_discover(struct pqos_cap_l2ca **r_cap,
                  const struct pqos_cpuinfo *cpu,
                  const enum pqos_interface iface)
{
        struct pqos_cap_l2ca *cap = NULL;
        int ret;

        cap = (struct pqos_cap_l2ca *)malloc(sizeof(*cap));
        if (cap == NULL)
                return PQOS_RETVAL_RESOURCE;

        switch (iface) {
        case PQOS_INTER_MSR:
                ret = hw_cap_l2ca_discover(cap, cpu);
                break;
#ifdef __linux__
        case PQOS_INTER_OS:
        case PQOS_INTER_OS_RESCTRL_MON:
                ret = os_cap_l2ca_discover(cap, cpu);
                break;
#endif
        default:
                ret = PQOS_RETVAL_RESOURCE;
                break;
        }

        if (ret == PQOS_RETVAL_OK)
                *r_cap = cap;
        else
                free(cap);

        return ret;
}

/**
 * @brief Discovers support of MBA
 *
 * @param[out] r_cap place to store MBA capabilities structure
 * @param[in] cpu detected cpu topology
 * @param[in] iface Selected interface
 *
 * @return Operation status
 * @retval PQOS_RETVAL_OK success
 */
static int
cap_mba_discover(struct pqos_cap_mba **r_cap,
                 const struct pqos_cpuinfo *cpu,
                 const enum pqos_interface iface)
{
        struct pqos_cap_mba *cap = NULL;
        int ret;

        cap = (struct pqos_cap_mba *)malloc(sizeof(*cap));
        if (cap == NULL)
                return PQOS_RETVAL_RESOURCE;

        switch (iface) {
        case PQOS_INTER_MSR:
                if (cpu->vendor == PQOS_VENDOR_AMD)
                        ret = amd_cap_mba_discover(cap, cpu);
                else
                        ret = hw_cap_mba_discover(cap, cpu);
                break;
#ifdef __linux__
        case PQOS_INTER_OS:
        case PQOS_INTER_OS_RESCTRL_MON:
                ret = os_cap_mba_discover(cap, cpu);
                break;
#endif
        default:
                ret = PQOS_RETVAL_RESOURCE;
                break;
        }

        if (ret == PQOS_RETVAL_OK)
                *r_cap = cap;
        else
                free(cap);

        return ret;
}

/**
 * @brief Runs detection of platform monitoring and allocation capabilities
 *
 * @param p_cap place to store allocated capabilities structure
 * @param cpu detected cpu topology
 * @param inter selected pqos interface
 *
 * @return Operation status
 * @retval PQOS_RETVAL_OK success
 */
static int
discover_capabilities(struct pqos_cap **p_cap,
                      const struct pqos_cpuinfo *cpu,
                      enum pqos_interface inter)
{
        struct pqos_cap_mon *det_mon = NULL;
        struct pqos_cap_l3ca *det_l3ca = NULL;
        struct pqos_cap_l2ca *det_l2ca = NULL;
        struct pqos_cap_mba *det_mba = NULL;
        struct pqos_cap *_cap = NULL;
        unsigned sz = 0;
        int ret = PQOS_RETVAL_RESOURCE;

        /**
         * Monitoring init
         */
        if (inter == PQOS_INTER_MSR)
                ret = hw_cap_mon_discover(&det_mon, cpu);
#ifdef __linux__
        else if (inter == PQOS_INTER_OS || inter == PQOS_INTER_OS_RESCTRL_MON)
                ret = os_cap_mon_discover(&det_mon, cpu);
#endif
        switch (ret) {
        case PQOS_RETVAL_OK:
                LOG_INFO("Monitoring capability detected\n");
                sz += sizeof(struct pqos_capability);
                break;
        case PQOS_RETVAL_RESOURCE:
                LOG_INFO("Monitoring capability not detected\n");
                break;
        default:
                LOG_ERROR("Error encounter in monitoring discovery!\n");
                ret = PQOS_RETVAL_ERROR;
                goto error_exit;
        }

        /**
         * L3 Cache allocation init
         */
        ret = cap_l3ca_discover(&det_l3ca, cpu, inter);
        switch (ret) {
        case PQOS_RETVAL_OK:
                LOG_INFO("L3CA capability detected\n");
                LOG_INFO("L3 CAT details: CDP support=%d, CDP on=%d, "
                         "#COS=%u, #ways=%u, ways contention bit-mask 0x%lx\n",
                         det_l3ca->cdp, det_l3ca->cdp_on, det_l3ca->num_classes,
                         det_l3ca->num_ways,
                         (unsigned long)det_l3ca->way_contention);
                LOG_INFO("L3 CAT details: cache size %u bytes, "
                         "way size %u bytes\n",
                         det_l3ca->way_size * det_l3ca->num_ways,
                         det_l3ca->way_size);
                sz += sizeof(struct pqos_capability);
                break;
        case PQOS_RETVAL_RESOURCE:
                LOG_INFO("L3CA capability not detected\n");
                break;
        default:
                LOG_ERROR("Fatal error encounter in L3 CAT discovery!\n");
                ret = PQOS_RETVAL_ERROR;
                goto error_exit;
        }

        /**
         * L2 Cache allocation init
         */
        ret = cap_l2ca_discover(&det_l2ca, cpu, inter);
        switch (ret) {
        case PQOS_RETVAL_OK:
                LOG_INFO("L2CA capability detected\n");
                LOG_INFO("L2 CAT details: CDP support=%d, CDP on=%d, "
                         "#COS=%u, #ways=%u, ways contention bit-mask 0x%lx\n",
                         det_l2ca->cdp, det_l2ca->cdp_on, det_l2ca->num_classes,
                         det_l2ca->num_ways,
                         (unsigned long)det_l2ca->way_contention);
                LOG_INFO("L2 CAT details: cache size %u bytes, way size %u "
                         "bytes\n",
                         det_l2ca->way_size * det_l2ca->num_ways,
                         det_l2ca->way_size);
                sz += sizeof(struct pqos_capability);
                break;
        case PQOS_RETVAL_RESOURCE:
                LOG_INFO("L2CA capability not detected\n");
                break;
        default:
                LOG_ERROR("Fatal error encounter in L2 CAT discovery!\n");
                ret = PQOS_RETVAL_ERROR;
                goto error_exit;
        }

        /**
         * Memory bandwidth allocation init
         */
        ret = cap_mba_discover(&det_mba, cpu, inter);
        switch (ret) {
        case PQOS_RETVAL_OK:
                LOG_INFO("MBA capability detected\n");
                LOG_INFO("MBA details: "
                         "#COS=%u, %slinear, max=%u, step=%u\n",
                         det_mba->num_classes, det_mba->is_linear ? "" : "non-",
                         det_mba->throttle_max, det_mba->throttle_step);
                sz += sizeof(struct pqos_capability);
                break;
        case PQOS_RETVAL_RESOURCE:
                LOG_INFO("MBA capability not detected\n");
                break;
        default:
                LOG_ERROR("Fatal error encounter in MBA discovery!\n");
                ret = PQOS_RETVAL_ERROR;
                goto error_exit;
        }

        if (sz == 0) {
                LOG_ERROR("No Platform QoS capability discovered\n");
                ret = PQOS_RETVAL_ERROR;
                goto error_exit;
        }

        sz += sizeof(struct pqos_cap);
        _cap = (struct pqos_cap *)malloc(sz);
        if (_cap == NULL) {
                LOG_ERROR("Allocation error in %s()\n", __func__);
                ret = PQOS_RETVAL_ERROR;
                goto error_exit;
        }

        memset(_cap, 0, sz);
        _cap->mem_size = sz;
        _cap->version = PQOS_VERSION;

        if (det_mon != NULL) {
                _cap->capabilities[_cap->num_cap].type = PQOS_CAP_TYPE_MON;
                _cap->capabilities[_cap->num_cap].u.mon = det_mon;
                _cap->num_cap++;
                ret = PQOS_RETVAL_OK;
        }

        if (det_l3ca != NULL) {
                _cap->capabilities[_cap->num_cap].type = PQOS_CAP_TYPE_L3CA;
                _cap->capabilities[_cap->num_cap].u.l3ca = det_l3ca;
                _cap->num_cap++;
                ret = PQOS_RETVAL_OK;
        }

        if (det_l2ca != NULL) {
                _cap->capabilities[_cap->num_cap].type = PQOS_CAP_TYPE_L2CA;
                _cap->capabilities[_cap->num_cap].u.l2ca = det_l2ca;
                _cap->num_cap++;
                ret = PQOS_RETVAL_OK;
        }

        if (det_mba != NULL) {
                _cap->capabilities[_cap->num_cap].type = PQOS_CAP_TYPE_MBA;
                _cap->capabilities[_cap->num_cap].u.mba = det_mba;
                _cap->num_cap++;
                ret = PQOS_RETVAL_OK;
#ifdef __linux__
                /**
                 * Check status of MBA CTRL
                 */
                if (inter == PQOS_INTER_OS ||
                    inter == PQOS_INTER_OS_RESCTRL_MON) {
                        ret = os_cap_get_mba_ctrl(_cap, cpu, &det_mba->ctrl,
                                                  &det_mba->ctrl_on);
                        if (ret != PQOS_RETVAL_OK)
                                goto error_exit;
                }
#endif
        }

        (*p_cap) = _cap;

error_exit:
        if (ret != PQOS_RETVAL_OK) {
                if (det_mon != NULL)
                        free(det_mon);
                if (det_l3ca != NULL)
                        free(det_l3ca);
                if (det_l2ca != NULL)
                        free(det_l2ca);
                if (det_mba != NULL)
                        free(det_mba);
                if (_cap != NULL)
                        free(_cap);
        }

        return ret;
}

/**
 * @brief Converts enumeration value into string
 *
 * @param interface interface
 *
 * @return converted enumeration value into string
 */
static const char *
interface_to_string(enum pqos_interface interface)
{
        switch (interface) {
        case PQOS_INTER_MSR:
                return "MSR";
        case PQOS_INTER_OS:
                return "OS";
        case PQOS_INTER_OS_RESCTRL_MON:
                return "OS_RESCTRL_MON";
        case PQOS_INTER_AUTO:
                return "AUTO";
        default:
                return "Unknown";
        }
}

/**
 * @brief Detects interface
 *
 * @param requested_interface requested interface
 * @param interface detected interface
 *
 * @return Operation status
 * @retval PQOS_RETVAL_OK success
 */
static int
discover_interface(enum pqos_interface requested_interface,
                   enum pqos_interface *interface)
{
        char *environment = NULL;

        LOG_INFO("Requested interface: %s\n",
                 interface_to_string(requested_interface));

#ifdef __linux__
        if (requested_interface != PQOS_INTER_MSR &&
            requested_interface != PQOS_INTER_OS &&
            requested_interface != PQOS_INTER_OS_RESCTRL_MON &&
            requested_interface != PQOS_INTER_AUTO)
#else
        if (requested_interface != PQOS_INTER_MSR &&
            requested_interface != PQOS_INTER_AUTO)
#endif
                return PQOS_RETVAL_PARAM;

        environment = getenv("RDT_IFACE");
        if (environment != NULL) {
                if (strncasecmp(environment, "OS", 2) == 0) {
                        if (requested_interface != PQOS_INTER_OS &&
                            requested_interface != PQOS_INTER_AUTO) {
                                LOG_ERROR("Interface initialization error!\n"
                                          "Your system has been restricted "
                                          "to use the OS interface only!\n");
                                return PQOS_RETVAL_ERROR;
                        }

                        *interface = PQOS_INTER_OS;
                } else if (strncasecmp(environment, "MSR", 3) == 0) {
                        if (requested_interface != PQOS_INTER_MSR &&
                            requested_interface != PQOS_INTER_AUTO) {
                                LOG_ERROR("Interface initialization error!\n"
                                          "Your system has been restricted "
                                          "to use the MSR interface only!\n");
                                return PQOS_RETVAL_ERROR;
                        }

                        *interface = PQOS_INTER_MSR;
                } else {
                        LOG_ERROR("Interface initialization error!\n"
                                  "Invalid interface enforcement selection.\n");
                        return PQOS_RETVAL_ERROR;
                }
        } else if (requested_interface == PQOS_INTER_AUTO) {
#ifdef __linux__
                if (resctrl_is_supported() == PQOS_RETVAL_OK)
                        *interface = PQOS_INTER_OS;
                else
                        *interface = PQOS_INTER_MSR;
#else
                *interface = PQOS_INTER_MSR;
#endif
        } else {
                *interface = requested_interface;
        }

        LOG_INFO("Selected interface: %s\n", interface_to_string(*interface));
        return PQOS_RETVAL_OK;
}

/*
 * =======================================
 * =======================================
 *
 * initialize and shutdown
 *
 * =======================================
 * =======================================
 */
int
pqos_init(const struct pqos_config *config)
{
        int ret = PQOS_RETVAL_OK;
        unsigned i = 0, max_core = 0;
        int cat_init = 0, mon_init = 0;
        enum pqos_interface interface;

        if (config == NULL)
                return PQOS_RETVAL_PARAM;

        if (_pqos_api_init() != 0) {
                fprintf(stderr, "API lock initialization error!\n");
                return PQOS_RETVAL_ERROR;
        }

        _pqos_api_lock();

        ret = _pqos_check_init(0);
        if (ret != PQOS_RETVAL_OK) {
                _pqos_api_unlock();
                return ret;
        }

        ret = log_init(config->fd_log, config->callback_log,
                       config->context_log, config->verbose);
        if (ret != LOG_RETVAL_OK) {
                fprintf(stderr, "log_init() error\n");
                goto init_error;
        }

        ret = discover_interface(config->interface, &interface);
        if (ret != PQOS_RETVAL_OK) {
                LOG_ERROR("Cannot select the interface!\n");
                goto log_init_error;
        }

        /**
         * Topology not provided through config.
         * CPU discovery done through internal mechanism.
         */
        ret = cpuinfo_init(interface, &m_cpu);
        if (ret != 0 || m_cpu == NULL) {
                LOG_ERROR("cpuinfo_init() error %d\n", ret);
                ret = PQOS_RETVAL_ERROR;
                goto log_init_error;
        }

        /**
         * Find max core id in the topology
         */
        for (i = 0; i < m_cpu->num_cores; i++)
                if (m_cpu->cores[i].lcore > max_core)
                        max_core = m_cpu->cores[i].lcore;

        ret = machine_init(max_core);
        if (ret != PQOS_RETVAL_OK) {
                LOG_ERROR("machine_init() error %d\n", ret);
                goto cpuinfo_init_error;
        }

#ifdef __linux__
        if (interface == PQOS_INTER_OS ||
            interface == PQOS_INTER_OS_RESCTRL_MON) {
                ret = os_cap_init(interface);
                if (ret != PQOS_RETVAL_OK) {
                        LOG_ERROR("os_cap_init() error %d\n", ret);
                        goto machine_init_error;
                }
        } else if (access(RESCTRL_PATH "/cpus", F_OK) == 0)
                LOG_WARN("resctl filesystem mounted! Using MSR "
                         "interface may corrupt resctrl filesystem "
                         "and cause unexpected behaviour\n");
#endif

        ret = discover_capabilities(&m_cap, m_cpu, interface);
        if (ret != PQOS_RETVAL_OK) {
                LOG_ERROR("discover_capabilities() error %d\n", ret);
                goto machine_init_error;
        }

        ret = _pqos_utils_init(interface);
        if (ret != PQOS_RETVAL_OK) {
                fprintf(stderr, "Utils initialization error!\n");
                goto machine_init_error;
        }

        ret = api_init(interface, m_cpu->vendor);
        if (ret != PQOS_RETVAL_OK) {
                LOG_ERROR("_pqos_api_init() error %d\n", ret);
                goto machine_init_error;
        }

        m_interface = interface;

        ret = pqos_alloc_init(m_cpu, m_cap, config);
        switch (ret) {
        case PQOS_RETVAL_BUSY:
                LOG_ERROR("OS allocation init error!\n");
                goto machine_init_error;
        case PQOS_RETVAL_OK:
                LOG_DEBUG("allocation init OK\n");
                cat_init = 1;
                break;
        default:
                LOG_ERROR("allocation init error %d\n", ret);
                break;
        }

        /**
         * If monitoring capability has been discovered
         * then get max RMID supported by a CPU socket
         * and allocate memory for RMID table
         */
        ret = pqos_mon_init(m_cpu, m_cap, config);
        switch (ret) {
        case PQOS_RETVAL_RESOURCE:
                LOG_DEBUG("monitoring init aborted: feature not present\n");
                ret = PQOS_RETVAL_OK;
                break;
        case PQOS_RETVAL_OK:
                LOG_DEBUG("monitoring init OK\n");
                mon_init = 1;
                break;
        case PQOS_RETVAL_ERROR:
        default:
                LOG_ERROR("monitoring init error %d\n", ret);
                break;
        }

        if (cat_init == 0 && mon_init == 0) {
                LOG_ERROR("None of detected capabilities could be "
                          "initialized!\n");
                ret = PQOS_RETVAL_ERROR;
        }

machine_init_error:
        if (ret != PQOS_RETVAL_OK)
                (void)machine_fini();
cpuinfo_init_error:
        if (ret != PQOS_RETVAL_OK)
                (void)cpuinfo_fini();
log_init_error:
        if (ret != PQOS_RETVAL_OK)
                (void)log_fini();
init_error:
        if (ret != PQOS_RETVAL_OK) {
                if (m_cap != NULL) {
                        for (i = 0; i < m_cap->num_cap; i++)
                                free(m_cap->capabilities[i].u.generic_ptr);
                        free(m_cap);
                }
                m_cpu = NULL;
                m_cap = NULL;
        }

        if (ret == PQOS_RETVAL_OK)
                m_init_done = 1;

        _pqos_api_unlock();

        if (ret != PQOS_RETVAL_OK)
                _pqos_api_exit();

        return ret;
}

int
pqos_fini(void)
{
        int ret = PQOS_RETVAL_OK;
        int retval = PQOS_RETVAL_OK;
        unsigned i = 0;

        _pqos_api_lock();

        ret = _pqos_check_init(1);
        if (ret != PQOS_RETVAL_OK) {
                _pqos_api_unlock();
                _pqos_api_exit();
                return ret;
        }

        pqos_mon_fini();
        pqos_alloc_fini();

        ret = cpuinfo_fini();
        if (ret != 0) {
                retval = PQOS_RETVAL_ERROR;
                LOG_ERROR("cpuinfo_fini() error %d\n", ret);
        }

        ret = machine_fini();
        if (ret != PQOS_RETVAL_OK) {
                retval = ret;
                LOG_ERROR("machine_fini() error %d\n", ret);
        }

        ret = log_fini();
        if (ret != PQOS_RETVAL_OK)
                retval = ret;

        m_cpu = NULL;

        for (i = 0; i < m_cap->num_cap; i++)
                free(m_cap->capabilities[i].u.generic_ptr);
        free((void *)m_cap);
        m_cap = NULL;

        m_init_done = 0;

        _pqos_api_unlock();

        if (_pqos_api_exit() != 0)
                retval = PQOS_RETVAL_ERROR;

        return retval;
}

/**
 * =======================================
 * =======================================
 *
 * capabilities
 *
 * =======================================
 * =======================================
 */

int
pqos_cap_get(const struct pqos_cap **cap, const struct pqos_cpuinfo **cpu)
{
        int ret = PQOS_RETVAL_OK;

        if (cap == NULL && cpu == NULL)
                return PQOS_RETVAL_PARAM;

        _pqos_api_lock();

        ret = _pqos_check_init(1);
        if (ret != PQOS_RETVAL_OK) {
                _pqos_api_unlock();
                return ret;
        }

        if (cap != NULL) {
                *cap = _pqos_get_cap();
                ASSERT(*cap != NULL);
        }
        if (cpu != NULL) {
                *cpu = _pqos_get_cpu();
                ASSERT(*cpu != NULL);
        }

        _pqos_api_unlock();
        return PQOS_RETVAL_OK;
}

void
_pqos_cap_l3cdp_change(const enum pqos_cdp_config cdp)
{
        struct pqos_cap_l3ca *l3_cap = NULL;
        struct pqos_cap_l3ca cap;
        int ret;
        unsigned i;
        enum pqos_interface interface = _pqos_get_inter();

        ASSERT(cdp == PQOS_REQUIRE_CDP_ON || cdp == PQOS_REQUIRE_CDP_OFF ||
               cdp == PQOS_REQUIRE_CDP_ANY);
        ASSERT(m_cap != NULL);

        if (m_cap == NULL)
                return;

        for (i = 0; i < m_cap->num_cap && l3_cap == NULL; i++)
                if (m_cap->capabilities[i].type == PQOS_CAP_TYPE_L3CA)
                        l3_cap = m_cap->capabilities[i].u.l3ca;

        if (l3_cap == NULL)
                return;

        switch (interface) {
        case PQOS_INTER_MSR:
                ret = hw_cap_l3ca_discover(&cap, m_cpu);
                break;
#ifdef __linux__
        case PQOS_INTER_OS: /* fall through */
        case PQOS_INTER_OS_RESCTRL_MON:
                ret = os_cap_l3ca_discover(&cap, m_cpu);
                break;
#endif
        default:
                ret = PQOS_RETVAL_RESOURCE;
                break;
        }

        if (ret == PQOS_RETVAL_OK) {
                *l3_cap = cap;
                return;
        }

        if (cdp == PQOS_REQUIRE_CDP_ON && !l3_cap->cdp_on) {
                /* turn on */
                l3_cap->cdp_on = 1;
                l3_cap->num_classes = l3_cap->num_classes / 2;
        }

        if (cdp == PQOS_REQUIRE_CDP_OFF && l3_cap->cdp_on) {
                /* turn off */
                l3_cap->cdp_on = 0;
                l3_cap->num_classes = l3_cap->num_classes * 2;
        }
}

void
_pqos_cap_l2cdp_change(const enum pqos_cdp_config cdp)
{
        struct pqos_cap_l2ca *l2_cap = NULL;
        struct pqos_cap_l2ca cap;
        unsigned i;
        int ret;
        enum pqos_interface interface = _pqos_get_inter();

        ASSERT(cdp == PQOS_REQUIRE_CDP_ON || cdp == PQOS_REQUIRE_CDP_OFF ||
               cdp == PQOS_REQUIRE_CDP_ANY);
        ASSERT(m_cap != NULL);

        if (m_cap == NULL)
                return;

        for (i = 0; i < m_cap->num_cap && l2_cap == NULL; i++)
                if (m_cap->capabilities[i].type == PQOS_CAP_TYPE_L2CA)
                        l2_cap = m_cap->capabilities[i].u.l2ca;

        if (l2_cap == NULL)
                return;

        switch (interface) {
        case PQOS_INTER_MSR:
                ret = hw_cap_l2ca_discover(&cap, m_cpu);
                break;
#ifdef __linux__
        case PQOS_INTER_OS:
        case PQOS_INTER_OS_RESCTRL_MON:
                ret = os_cap_l2ca_discover(&cap, m_cpu);
                break;
#endif
        default:
                ret = PQOS_RETVAL_RESOURCE;
                break;
        }

        if (ret == PQOS_RETVAL_OK) {
                *l2_cap = cap;
                return;
        }

        if (cdp == PQOS_REQUIRE_CDP_ON && !l2_cap->cdp_on) {
                /* turn on */
                l2_cap->cdp_on = 1;
                l2_cap->num_classes = l2_cap->num_classes / 2;
        }

        if (cdp == PQOS_REQUIRE_CDP_OFF && l2_cap->cdp_on) {
                /* turn off */
                l2_cap->cdp_on = 0;
                l2_cap->num_classes = l2_cap->num_classes * 2;
        }
}

void
_pqos_cap_mba_change(const enum pqos_mba_config cfg)
{
        struct pqos_cap_mba *mba_cap = NULL;
        unsigned i;
#ifdef __linux__
        enum pqos_interface interface = _pqos_get_inter();
#endif

        ASSERT(cfg == PQOS_MBA_DEFAULT || cfg == PQOS_MBA_CTRL ||
               cfg == PQOS_MBA_ANY);
        ASSERT(m_cap != NULL);

        if (m_cap == NULL)
                return;

        for (i = 0; i < m_cap->num_cap && mba_cap == NULL; i++)
                if (m_cap->capabilities[i].type == PQOS_CAP_TYPE_MBA)
                        mba_cap = m_cap->capabilities[i].u.mba;

        if (mba_cap == NULL)
                return;

#ifdef __linux__
        /* refresh number of classes */
        if (interface == PQOS_INTER_OS ||
            interface == PQOS_INTER_OS_RESCTRL_MON) {
                int ret;
                unsigned num_classes;

                ret = resctrl_alloc_get_num_closids(&num_classes);
                if (ret == PQOS_RETVAL_OK)
                        mba_cap->num_classes = num_classes;
        }
#endif

        if (cfg == PQOS_MBA_DEFAULT)
                mba_cap->ctrl_on = 0;
        else if (cfg == PQOS_MBA_CTRL) {
#ifdef __linux__
                if (interface != PQOS_INTER_MSR)
                        mba_cap->ctrl = 1;
#endif
                mba_cap->ctrl_on = 1;
        }
}

const struct pqos_cap *
_pqos_get_cap(void)
{
        ASSERT(m_cap != NULL);
        return m_cap;
}

const struct pqos_cpuinfo *
_pqos_get_cpu(void)
{
        ASSERT(m_cpu != NULL);
        return m_cpu;
}

int
_pqos_cap_get_type(const enum pqos_cap_type type,
                   const struct pqos_capability **cap_item)
{
        return pqos_cap_get_type(m_cap, type, cap_item);
}

/**
 * =======================================
 * =======================================
 *
 * interface
 *
 * =======================================
 * =======================================
 */

int
pqos_inter_get(enum pqos_interface *interface)
{
        int ret = PQOS_RETVAL_OK;

        if (interface == NULL)
                return PQOS_RETVAL_PARAM;

        _pqos_api_lock();

        ret = _pqos_check_init(1);
        if (ret != PQOS_RETVAL_OK) {
                _pqos_api_unlock();
                return ret;
        }

        *interface = _pqos_get_inter();

        _pqos_api_unlock();
        return PQOS_RETVAL_OK;
}

enum pqos_interface
_pqos_get_inter(void)
{
        return m_interface;
}
