################################################################################
# BSD LICENSE
#
# Copyright(c) 2019-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.
################################################################################

"""
The module defines PqosCap which can be used to read PQoS capabilities.
"""

from __future__ import absolute_import, division, print_function
import ctypes

from pqos.common import pqos_handle_error
from pqos.pqos import Pqos


class CPqosCapabilityL3(ctypes.Structure):
    "pqos_cap_l3ca structure"
    # pylint: disable=too-few-public-methods

    _fields_ = [
        ('mem_size', ctypes.c_uint),
        ('num_classes', ctypes.c_uint),
        ('num_ways', ctypes.c_uint),
        ('way_size', ctypes.c_uint),
        ('way_contention', ctypes.c_uint64),
        ('cdp', ctypes.c_int),
        ('cdp_on', ctypes.c_int),
    ]


class CPqosCapabilityL2(ctypes.Structure):
    "pqos_cap_l2ca structure"
    # pylint: disable=too-few-public-methods

    _fields_ = [
        ('mem_size', ctypes.c_uint),
        ('num_classes', ctypes.c_uint),
        ('num_ways', ctypes.c_uint),
        ('way_size', ctypes.c_uint),
        ('way_contention', ctypes.c_uint64),
        ('cdp', ctypes.c_int),
        ('cdp_on', ctypes.c_int),
    ]


class CPqosCapabilityMBA(ctypes.Structure):
    "pqos_cap_mba structure"
    # pylint: disable=too-few-public-methods

    _fields_ = [
        ('mem_size', ctypes.c_uint),
        ('num_classes', ctypes.c_uint),
        ('throttle_max', ctypes.c_uint),
        ('throttle_step', ctypes.c_uint),
        ('is_linear', ctypes.c_int),
        ('ctrl', ctypes.c_int),
        ('ctrl_on', ctypes.c_int),
    ]


class CPqosMonitor(ctypes.Structure):
    "pqos_monitor structure"
    # pylint: disable=too-few-public-methods

    PQOS_MON_EVENT_L3_OCCUP = 1
    PQOS_MON_EVENT_LMEM_BW = 2
    PQOS_MON_EVENT_TMEM_BW = 4
    PQOS_MON_EVENT_RMEM_BW = 8
    RESERVED1 = 0x1000
    RESERVED2 = 0x2000
    PQOS_PERF_EVENT_LLC_MISS = 0x4000
    PQOS_PERF_EVENT_IPC = 0x8000

    _fields_ = [
        ('type', ctypes.c_int),
        ('max_rmid', ctypes.c_uint),
        ('scale_factor', ctypes.c_uint32),
        ('counter_length', ctypes.c_uint),
    ]


class CPqosCapabilityMonitoring(ctypes.Structure):
    "pqos_cap_mon structure"
    # pylint: disable=too-few-public-methods

    _fields_ = [
        ('mem_size', ctypes.c_uint),
        ('max_rmid', ctypes.c_uint),
        ('l3_size', ctypes.c_uint),
        ('num_events', ctypes.c_uint),
        ('events', CPqosMonitor * 0),
    ]


class CPqosCapabilityUnion(ctypes.Union):
    "Union from pqos_capability structure"
    # pylint: disable=too-few-public-methods

    _fields_ = [
        ('mon', ctypes.POINTER(CPqosCapabilityMonitoring)),
        ('l3ca', ctypes.POINTER(CPqosCapabilityL3)),
        ('l2ca', ctypes.POINTER(CPqosCapabilityL2)),
        ('mba', ctypes.POINTER(CPqosCapabilityMBA)),
        ('generic_ptr', ctypes.c_void_p),
    ]


class CPqosCapability(ctypes.Structure):
    "pqos_capability structure"
    # pylint: disable=too-few-public-methods

    PQOS_CAP_TYPE_MON = 0
    PQOS_CAP_TYPE_L3CA = 1
    PQOS_CAP_TYPE_L2CA = 2
    PQOS_CAP_TYPE_MBA = 3
    PQOS_CAP_TYPE_NUMOF = 4

    _fields_ = [
        ('type', ctypes.c_int),
        ('u', CPqosCapabilityUnion)
    ]


class CPqosCap(ctypes.Structure):
    "pqos_cap structure"
    # pylint: disable=too-few-public-methods

    _fields_ = [
        ('mem_size', ctypes.c_uint),
        ('version', ctypes.c_uint),
        ('num_cap', ctypes.c_uint),
        ('capabilities', CPqosCapability * 0)
    ]


class PqosCapabilityMonitoring(object):
    "PQoS monitoring capability"
    # pylint: disable=too-few-public-methods

    def __init__(self):
        self.mem_size = 0    # byte size of the structure
        self.max_rmid = 0    # max RMID supported by socket
        self.l3_size = 0     # L3 cache size in bytes
        self.events = []     # a list of supported events


class PqosCapabilityL3Ca(object):
    "PQoS L3 cache allocation capability"
    # pylint: disable=too-few-public-methods

    def __init__(self):
        self.mem_size = 0           # byte size of the structure
        self.num_classes = 0        # number of classes of service
        self.num_ways = 0           # number of cache ways
        self.way_size = 0           # way size in bytes
        self.way_contention = 0     # ways contention bit mask
        self.cdp = False            # code data prioritization feature support
        self.cdp_on = False         # code data prioritization on or off


class PqosCapabilityL2Ca(object):
    "PQoS L2 cache allocation capability"
    # pylint: disable=too-few-public-methods

    def __init__(self):
        self.mem_size = 0           # byte size of the structure
        self.num_classes = 0        # number of classes of service
        self.num_ways = 0           # number of cache ways
        self.way_size = 0           # way size in bytes
        self.way_contention = 0     # ways contention bit mask
        self.cdp = False            # code data prioritization feature support
        self.cdp_on = False         # code data prioritization on or off


class PqosCapabilityMba(object):
    "PQoS memory bandwidth allocation capability"
    # pylint: disable=too-few-public-methods

    def __init__(self):
        self.mem_size = 0              # byte size of the structure
        self.num_classes = 0           # number of classes of service
        self.throttle_max = 0          # the max MBA can be throttled
        self.throttle_step = 0         # MBA granularity
        self.is_linear = False         # the type of MBA linear/nonlinear
        self.ctrl = False              # MBA controller support
        self.ctrl_on = False           # MBA controller on or off


def _get_tristate_bool(c_val):
    "Converts tri-state integer value -1, 0 or 1 to None, False or True."
    tristate_map = {
        1: True,
        0: False,
        -1: None
    }

    return tristate_map.get(c_val)


def _get_cap_mon(p_capability):
    """
    Converts low-level pqos_cap_mon structure to
    high-level PqosCapabilityMonitoring object.
    """
    c_capability = p_capability.contents
    capability = PqosCapabilityMonitoring()
    capability.mem_size = c_capability.mem_size
    capability.max_rmid = c_capability.max_rmid
    capability.l3_size = c_capability.l3_size

    events_ptr = ctypes.cast(c_capability.events, ctypes.POINTER(CPqosMonitor))

    for i in range(c_capability.num_events):
        capability.events.append(events_ptr[i])

    return capability


def _get_cap_l3ca(p_capability):
    """
    Converts low-level pqos_cap_l3ca structure to
    high-level PqosCapabilityL3Ca object.
    """
    c_capability = p_capability.contents
    capability = PqosCapabilityL3Ca()
    capability.mem_size = c_capability.mem_size
    capability.num_classes = c_capability.num_classes
    capability.num_ways = c_capability.num_ways
    capability.way_size = c_capability.way_size
    capability.way_contention = c_capability.way_contention
    capability.cdp = _get_tristate_bool(c_capability.cdp)
    capability.cdp_on = _get_tristate_bool(c_capability.cdp_on)
    return capability


def _get_cap_l2ca(p_capability):
    """
    Converts low-level pqos_cap_l2ca structure to
    high-level PqosCapabilityL2Ca object.
    """
    c_capability = p_capability.contents
    capability = PqosCapabilityL2Ca()
    capability.mem_size = c_capability.mem_size
    capability.num_classes = c_capability.num_classes
    capability.num_ways = c_capability.num_ways
    capability.way_size = c_capability.way_size
    capability.way_contention = c_capability.way_contention
    capability.cdp = _get_tristate_bool(c_capability.cdp)
    capability.cdp_on = _get_tristate_bool(c_capability.cdp_on)
    return capability


def _get_cap_mba(p_capability):
    """
    Converts low-level pqos_cap_mba structure to
    high-level PqosCapabilityMba object.
    """
    c_capability = p_capability.contents
    capability = PqosCapabilityMba()
    capability.mem_size = c_capability.mem_size
    capability.num_classes = c_capability.num_classes
    capability.throttle_max = c_capability.throttle_max
    capability.throttle_step = c_capability.throttle_step
    capability.is_linear = c_capability.is_linear == 1
    capability.ctrl = _get_tristate_bool(c_capability.ctrl)
    capability.ctrl_on = _get_tristate_bool(c_capability.ctrl_on)
    return capability


def pqos_get_type_enum(type_str):
    "Converts capability type string to pqos_capability's enum."
    type_enum_map = {
        'mon': CPqosCapability.PQOS_CAP_TYPE_MON,
        'l3ca': CPqosCapability.PQOS_CAP_TYPE_L3CA,
        'l2ca': CPqosCapability.PQOS_CAP_TYPE_L2CA,
        'mba': CPqosCapability.PQOS_CAP_TYPE_MBA
    }

    return type_enum_map[type_str.lower()]


def _get_capability(cap_item, type_str):
    "Converts capability type string to pqos_capability's enum."
    type_cls_map = {
        'mon': (_get_cap_mon, lambda c: c.u.mon),
        'l3ca': (_get_cap_l3ca, lambda c: c.u.l3ca),
        'l2ca': (_get_cap_l2ca, lambda c: c.u.l2ca),
        'mba': (_get_cap_mba, lambda c: c.u.mba)
    }
    capability_func, cap_item_func = type_cls_map[type_str.lower()]
    return capability_func(cap_item_func(cap_item))


class PqosCap(object):
    "PQoS capabilities"

    def __init__(self):
        "Initializes capabilities, calls pqos_cap_get."
        self.pqos = Pqos()
        self.p_cap = ctypes.POINTER(CPqosCap)()
        ret = self.pqos.lib.pqos_cap_get(ctypes.byref(self.p_cap), None)
        pqos_handle_error('pqos_cap_get', ret)

    def get_type(self, type_str):
        """Retrieves a type of capability from a cap structure.

        Parameters:
            type_str: a string indicating a type of capability, available
                      options: mon, l3ca, l2ca and mba
        """
        type_enum = pqos_get_type_enum(type_str)
        p_cap_item = ctypes.POINTER(CPqosCapability)()
        ret = self.pqos.lib.pqos_cap_get_type(self.p_cap, type_enum,
                                              ctypes.byref(p_cap_item))
        pqos_handle_error('pqos_cap_get_type', ret)

        cap_item = p_cap_item.contents
        capability = _get_capability(cap_item, type_str)
        return capability

    def get_l3ca_cos_num(self):
        """
        Retrieves number of L3 allocation classes of service from
        a cap structure.
        """
        cos_num = ctypes.c_uint(0)
        ret = self.pqos.lib.pqos_l3ca_get_cos_num(self.p_cap,
                                                  ctypes.byref(cos_num))
        pqos_handle_error('pqos_l3ca_get_cos_num', ret)
        return cos_num.value

    def get_l2ca_cos_num(self):
        """
        Retrieves number of L2 allocation classes of service from
        a cap structure.
        """
        cos_num = ctypes.c_uint(0)
        ret = self.pqos.lib.pqos_l2ca_get_cos_num(self.p_cap,
                                                  ctypes.byref(cos_num))
        pqos_handle_error('pqos_l2ca_get_cos_num', ret)
        return cos_num.value

    def get_mba_cos_num(self):
        """
        Retrieves number of memory B/W allocation classes of service from
        a cap structure.
        """
        cos_num = ctypes.c_uint(0)
        ret = self.pqos.lib.pqos_mba_get_cos_num(self.p_cap,
                                                 ctypes.byref(cos_num))
        pqos_handle_error('pqos_mba_get_cos_num', ret)
        return cos_num.value

    def is_l3ca_cdp_enabled(self):
        "Retrieves L3 CDP status."
        supported = ctypes.c_int(0)
        enabled = ctypes.c_int(0)
        ret = self.pqos.lib.pqos_l3ca_cdp_enabled(self.p_cap,
                                                  ctypes.byref(supported),
                                                  ctypes.byref(enabled))
        pqos_handle_error('pqos_l3ca_cdp_enabled', ret)
        return (_get_tristate_bool(supported.value),
                _get_tristate_bool(enabled.value))

    def is_l2ca_cdp_enabled(self):
        "Retrieves L2 CDP status."
        supported = ctypes.c_int(0)
        enabled = ctypes.c_int(0)
        ret = self.pqos.lib.pqos_l2ca_cdp_enabled(self.p_cap,
                                                  ctypes.byref(supported),
                                                  ctypes.byref(enabled))
        pqos_handle_error('pqos_l2ca_cdp_enabled', ret)
        return (_get_tristate_bool(supported.value),
                _get_tristate_bool(enabled.value))

    def is_mba_ctrl_enabled(self):
        "Retrieves MBA CTRL status."
        supported = ctypes.c_int(0)
        enabled = ctypes.c_int(0)
        ret = self.pqos.lib.pqos_mba_ctrl_enabled(self.p_cap,
                                                  ctypes.byref(supported),
                                                  ctypes.byref(enabled))
        pqos_handle_error('pqos_mba_ctrl_enabled', ret)
        return (_get_tristate_bool(supported.value),
                _get_tristate_bool(enabled.value))
