# Copyright (c) Sep 2019 Mellanox Technologies LTD. All rights reserved.
# Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# This software is available to you under a choice of one of two
# licenses.  You may choose to be licensed under the terms of the GNU
# General Public License (GPL) Version 2, available from the file
# COPYING in the main directory of this source tree, or the
# OpenIB.org BSD license below:
#
#     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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# 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.
# --
from __future__ import print_function
import sys
import os
import string
import subprocess
import re
import platform
import regaccess
import time
import ctypes
from enum import Enum
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'mft_core', 'device', 'device_info', 'wrapper'))
import device_info_wrapper

# class TracersDeviceInfo exists since fwtrace and fwparse needed additional functions that are not in DeviceInfoWrapper.cpp.


class TracersDeviceInfo:
    def __init__(self, devId, devName=None):
        self.device_info = ctypes.POINTER(ctypes.c_void_p)()  # create a pointer to a void pointer
        if (devId == 0):
            devId = device_info_wrapper.get_device_id_from_str(devName)
        device_info_wrapper.create_device_info(ctypes.byref(self.device_info), devId)  # initialize device_info

        self.name = device_info_wrapper.get_device_name(self.device_info)
        self.fw_str_db_signature_exists = device_info_wrapper.fw_str_db_signature_exists(self.device_info)
        self.fw_str_db_signature_addr = self.get_fw_str_db_signature_addr()
        self.max_apu = device_info_wrapper.get_max_apu(self.device_info)
        self.max_num_of_tiles = device_info_wrapper.get_max_num_of_tiles(self.device_info)
        self.max_irisc_per_tile = device_info_wrapper.get_max_irisc_per_tile(self.device_info)
        self.irisc_start_addr = device_info_wrapper.get_iris_start_addr(self.device_info)
        self.irisc_step = device_info_wrapper.get_iris_step(self.device_info)
        self.tile_start = device_info_wrapper.get_tile_start(self.device_info)
        self.tile_step = device_info_wrapper.get_tile_step(self.device_info)
        self.apu_start_addr = device_info_wrapper.get_apu_start_addr_step(self.device_info)
        self.apu_step = device_info_wrapper.get_apu_step(self.device_info)
        self.apu_max_num_of_steps = device_info_wrapper.get_apu_max_num_of_steps(self.device_info)
        self.supports_phy_uc = device_info_wrapper.supports_phy_uc(self.device_info)
        self.mask_classes = self.get_mask_classes()  # list of (trace type name, start_bit)
        self.default_tracer_mode = self.get_default_tracer_mode()
        self.is_dynamic_device_without_irisc_id = device_info_wrapper.is_dynamic_device_without_irisc_id(self.device_info)
        self.max_main_irisc = device_info_wrapper.get_max_main_irisc(self.device_info)


# the functions below do not exist in DeviceInfoWrapper.cpp. they are needed only in fwtrace and fwparse.
    def get_mask_classes(self):
        if device_info_wrapper.is_switch(self.device_info) or device_info_wrapper.is_retimer(self.device_info):
            return list(FwTraceUtilities.SWITCH_MASK_CLASSES)
        if device_info_wrapper.is_nic(self.device_info):
            return list(FwTraceUtilities.HCA_MASK_CLASSES)

    def get_fw_str_db_signature_addr(self):
        if device_info_wrapper.is_switch(self.device_info) or device_info_wrapper.is_retimer(self.device_info):
            return (0xfffff, 0, 16)
        if device_info_wrapper.is_nic(self.device_info):
            return (0x24780, 0, 16)

    def get_default_tracer_mode(self):
        if device_info_wrapper.is_switch(self.device_info) or device_info_wrapper.is_retimer(self.device_info):
            return "FIFO"
        if device_info_wrapper.is_nic(self.device_info):
            return "MEM"
        return None  # should not get here.


class FwTraceUtilities(object):

    SWITCH_MASK_CLASSES = []
    HCA_MASK_CLASSES = [
        ("DEBUG_INIT", 0), ("INIT", 1), ("ICM", 2), ("ICM_FREE_LIST", 3),
        ("HOST_MNG", 4), ("CMD_IF", 5), ("PHY_IB", 6), ("PHY_RX_ADAP", 7),
        ("LIBFHI", 8), ("PHY_COMMON", 9), ("PHY_GENERAL", 9), ("PHY_MANAGER", 10),
        ("PWR", 11), ("FLR", 12), ("ICM_ACCESS", 13),
        ("MAD", 14), ("EMAD", 14), ("RXT_CHECKS", 15), ("I2C", 16), ("QOS", 16), ("TRANSPORT", 17),
        ("FW_LL", 18), ("RX_ERRORS", 19), ("CMD_DRIVER", 20), ("PROFILING", 21), ("UAPP", 21),
        ("MANAGEMENT", 22), ("FLASH", 23), ("ACCUR_SCHED_PCI", 23), ("STEERING", 24),
        ("IFARM", 25), ("ICMD", 26), ("PCI", 27), ("DC_CLEANUP", 28), ("GENERAL", 28),
        ("PHY_ETH", 29), ("VIRT", 30), ("VQOS", 30), ("NODNIC", 31), ("ACCUR_SCHED_SX", 31)]  # list of (trace type name, start_bit)

    @classmethod
    def _cmd_exec(self, cmd):
        """
        Execute a command line and return the state and the output
        of the command
        """
        p = subprocess.Popen(cmd,
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             universal_newlines=False,
                             shell=True)
        output = p.communicate()
        stat = p.wait()
        return (stat, output[0].decode('utf-8'), output[1].decode('utf-8'))  # RC, Stdout, Stderr

    @classmethod
    def _is_dev_dbdf_format(self, dev):
        """
        Check if the input has a bdf format with domain
        If bdf format with domain format return True, otherwise return False
        """
        pat = r"[0-9,A-F,a-f]{4}:[0-9,A-F,a-f]{2}:[0-9,A-F,a-f]{2}\.[0-9,A-F,a-f]{1,2}"
        if re.match(pat, dev):
            return True
        return False

    @classmethod
    def _is_dev_bdf_format(self, dev):
        """
        Check if the input has a bdf format
        If bdf format return True, otherwise return False
        """
        pat = r"[0-9,A-F,a-f]{2}:[0-9,A-F,a-f]{2}\.[0-9,A-F,a-f]{1,2}"
        if re.match(pat, dev):
            return True
        return False

    @classmethod
    def _add_domain_to_address(self, dev_addr):
        """
        Add the domain "0000:" to the given address
        """
        if len(dev_addr.split(":")) == 2:
            return "0000:" + dev_addr
        return dev_addr

    @staticmethod
    def get_dev_dbdf(device_name):
        """
        retrieve the BDF according to the device name
        """
        if FwTraceUtilities._is_dev_dbdf_format(device_name):
            return device_name
        if FwTraceUtilities._is_dev_bdf_format(device_name):
            return FwTraceUtilities._add_domain_to_address(device_name)

        operatingSys = platform.system()

        if operatingSys == "FreeBSD":
            if not (device_name.startswith("pci")):
                raise RuntimeError("Unexpected device name format")
            return device_name[3:]
        elif operatingSys == "Linux":
            cmd = "mdevices_info -vv"
            (rc, out, _) = FwTraceUtilities._cmd_exec(cmd)
            if rc != 0:
                raise RuntimeError("Failed to get device PCI address")
            # extract bdf
            bdf = None
            for line in out.split('\n'):
                if device_name in line:
                    if len(line.split()) > 2:
                        bdf = line.split()[2]
            if not bdf:
                raise RuntimeError("Failed to get device PCI Address")
            return FwTraceUtilities._add_domain_to_address(bdf)
        elif operatingSys == "Windows":
            cmd = "mdevices status -vv"
            (rc, out, _) = FwTraceUtilities._cmd_exec(cmd)
            if rc != 0:
                raise RuntimeError("Failed to get device PCI address")
            # extract bdf
            bdf = None
            for line in out.split('\n'):
                l = line.split()
                if (len(l) > 1) and (device_name in l[0]) and ("seg:bus:dev.fn" in l[1]):
                    bdf = line.split('=')[1]
            if not bdf:
                raise RuntimeError("Failed to get device PCI Address")
            return bdf
        else:
            raise RuntimeError("Unsupported OS")

    @staticmethod
    def is_driver_mem_mode_supported():
        """
        Check if driver mem mode is suported
        If supported return True, otherwise return False
        """
        is_supported = False

        if os.name == "nt":
            # future capability - windows
            pass
        else:
            if os.path.exists("/sys/kernel/debug/tracing/events/mlx5/fw_tracer/") or \
               os.path.exists("/sys/kernel/debug/tracing/events/mlx5/mlx5_fw/"):
                is_supported = True

        return is_supported

    @staticmethod
    def is_secure_fw(mst_device):
        """
        Check if the firmware is secure or not
        If secure return True, otherwise return False
        """
        is_secure = False
        if mst_device is None:
            return is_secure

        # in case that reg access fail, we cant determain a secure fw and return false
        try:
            reg_access_obj = regaccess.RegAccess(mst_device)
            is_secure = reg_access_obj.getSecureFWStatus()
        except BaseException:
            is_secure = False

        return is_secure

    @staticmethod
    def ts_to_real_ts(ts, freq):
        """
        The method calculate a real time stamp
        and return is as [hh:mm:ss:nsec] format
        """
        if freq <= 0:
            raise RuntimeError("device frequency is not above Zero - can't calc real time stamp")

        nano_seconds = int(1000 / freq) * ts
        # calc hours
        time_in_seconds = int(nano_seconds / 1000000000)
        hours = int(time_in_seconds / 3600)
        # calc minutes
        time_in_seconds = time_in_seconds - (hours * 3600)
        minutes = int(time_in_seconds / 60)
        # calc seconds
        time_in_seconds = time_in_seconds - (minutes * 60)
        seconds = time_in_seconds
        # calc the reminder in nano seconds
        nsecs = nano_seconds - int(nano_seconds / 1000000000) * 1000000000

        return "{:02d}:{:02d}:{:02d}:{:09d}".format(hours, minutes, seconds, nsecs)

    @staticmethod
    def get_device_frequency(device):
        """
        The method call mlxuptime, parse
        the device frequency and return it as integer
        (mlxuptime can take 1 sec)
        """
        cmd = "mlxuptime -d" + device
        (rc, out, _) = FwTraceUtilities._cmd_exec(cmd)
        freq_num = 0
        if rc != 0:
            raise RuntimeError("Failed to get device frequency (necessary for real ts calculation)")
        else:
            for line in out.split('\n'):
                if "Measured core frequency" in line:
                    freq_str = line.split(": ")[1].split(" ")[0]
                    freq_num = int(round(float(freq_str)))
                    break

        return freq_num

##########################################


if __name__ == "__main__":
    sys.exit(ExtractStrings())
