/*
 * Copyright (C) 2025-2026 Intel Corporation
 *
 * SPDX-License-Identifier: MIT
 *
 */

#include "shared/source/os_interface/linux/pmt_util.h"

#include "level_zero/sysman/source/shared/linux/pmt/sysman_pmt.h"
#include "level_zero/sysman/source/shared/linux/product_helper/sysman_product_helper_hw.h"
#include "level_zero/sysman/source/shared/linux/product_helper/sysman_product_helper_hw.inl"
#include "level_zero/sysman/source/shared/linux/zes_os_sysman_imp.h"
#include "level_zero/sysman/source/sysman_const.h"
#include "level_zero/zes_intel_gpu_sysman.h"

namespace L0 {
namespace Sysman {
constexpr static auto gfxProduct = IGFX_CRI;

#include "level_zero/sysman/source/shared/linux/product_helper/sysman_product_helper_xe_hp_and_later.inl"
#include "level_zero/sysman/source/shared/product_helper/sysman_os_agnostic_product_helper_xe2_and_later.inl"

constexpr static uint32_t memoryMsuCount = 20;
constexpr static uint32_t busWidthPerChannelInBytes = 2; // 16 bits = 2 bytes
constexpr static uint32_t transactionSize = 64;
constexpr static uint32_t memoryBridgeCount = 2;

// XTAL clock frequency is denoted as an integer between [0-3] with a predefined value for each number.
// This vector defines the predefined value for each integer represented by the index of the vector.
static const std::vector<double> indexToXtalClockFrequencyMap = {24, 19.2, 38.4, 25};

static std::map<std::string, std::map<std::string, uint64_t>> guidToKeyOffsetMap = {
    {"0x1e2fa030", // CRI PUNIT rev 0
     {{"ACCUM_PACKAGE_ENERGY", 48},
      {"ACCUM_PSYS_ENERGY", 52},
      {"AVERAGE_POWER_CONTAINER", 136},
      {"GPU_BOARD_TEMPERATURE", 176},
      {"INSTANTANEOUS_POWER_CONTAINER", 128},
      {"VCCGT_ENERGY_ACCUMULATOR", 44},
      {"VCCDDRQ_ENERGY_ACCUMULATOR", 188},
      {"VR_TEMPERATURE_0", 200},
      {"VR_TEMPERATURE_1", 204},
      {"VR_TEMPERATURE_2", 208},
      {"VR_TEMPERATURE_3", 212},
      {"VRAM_BANDWIDTH", 56},
      {"XTAL_COUNT", 1016},
      {"XTAL_CLK_FREQUENCY", 4}}},
    {"0x5e2fa230", // CRI OOBMSM Rev 0
     {{"SOC_TOPDIE_TEMPERATURE", 128},
      {"VRAM_TEMPERATURE", 132},
      {"PCIE_RECEIVE_BYTES", 520},
      {"PCIE_TRANSMIT_BYTES", 528},
      {"PCIE_RECEIVE_PACKETS", 536},
      {"PCIE_TRANSMIT_PACKETS", 544},
      {"LPDDR_TELEM_CAPTURE_TIMESTAMP", 664},
      {"MEMSS0_PERF_CTR_MB0_CFI_NUM_READ_REQ", 688},
      {"MEMSS1_PERF_CTR_MB0_CFI_NUM_READ_REQ", 768},
      {"MEMSS2_PERF_CTR_MB0_CFI_NUM_READ_REQ", 848},
      {"MEMSS3_PERF_CTR_MB0_CFI_NUM_READ_REQ", 928},
      {"MEMSS4_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1008},
      {"MEMSS5_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1088},
      {"MEMSS6_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1168},
      {"MEMSS7_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1248},
      {"MEMSS8_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1328},
      {"MEMSS9_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1408},
      {"MEMSS10_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1488},
      {"MEMSS11_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1568},
      {"MEMSS12_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1648},
      {"MEMSS13_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1728},
      {"MEMSS14_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1808},
      {"MEMSS15_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1888},
      {"MEMSS16_PERF_CTR_MB0_CFI_NUM_READ_REQ", 1968},
      {"MEMSS17_PERF_CTR_MB0_CFI_NUM_READ_REQ", 2048},
      {"MEMSS18_PERF_CTR_MB0_CFI_NUM_READ_REQ", 2128},
      {"MEMSS19_PERF_CTR_MB0_CFI_NUM_READ_REQ", 2208},
      {"MEMSS0_PERF_CTR_MB1_CFI_NUM_READ_REQ", 728},
      {"MEMSS1_PERF_CTR_MB1_CFI_NUM_READ_REQ", 808},
      {"MEMSS2_PERF_CTR_MB1_CFI_NUM_READ_REQ", 888},
      {"MEMSS3_PERF_CTR_MB1_CFI_NUM_READ_REQ", 968},
      {"MEMSS4_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1048},
      {"MEMSS5_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1128},
      {"MEMSS6_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1208},
      {"MEMSS7_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1288},
      {"MEMSS8_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1368},
      {"MEMSS9_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1448},
      {"MEMSS10_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1528},
      {"MEMSS11_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1608},
      {"MEMSS12_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1688},
      {"MEMSS13_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1768},
      {"MEMSS14_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1848},
      {"MEMSS15_PERF_CTR_MB1_CFI_NUM_READ_REQ", 1928},
      {"MEMSS16_PERF_CTR_MB1_CFI_NUM_READ_REQ", 2008},
      {"MEMSS17_PERF_CTR_MB1_CFI_NUM_READ_REQ", 2088},
      {"MEMSS18_PERF_CTR_MB1_CFI_NUM_READ_REQ", 2168},
      {"MEMSS19_PERF_CTR_MB1_CFI_NUM_READ_REQ", 2248},
      {"MEMSS0_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 680},
      {"MEMSS1_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 760},
      {"MEMSS2_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 840},
      {"MEMSS3_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 920},
      {"MEMSS4_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1000},
      {"MEMSS5_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1080},
      {"MEMSS6_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1160},
      {"MEMSS7_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1240},
      {"MEMSS8_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1320},
      {"MEMSS9_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1400},
      {"MEMSS10_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1480},
      {"MEMSS11_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1560},
      {"MEMSS12_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1640},
      {"MEMSS13_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1720},
      {"MEMSS14_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1800},
      {"MEMSS15_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1880},
      {"MEMSS16_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 1960},
      {"MEMSS17_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 2040},
      {"MEMSS18_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 2120},
      {"MEMSS19_PERF_CTR_MB0_CFI_NUM_WRITE_REQ", 2200},
      {"MEMSS0_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 720},
      {"MEMSS1_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 800},
      {"MEMSS2_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 880},
      {"MEMSS3_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 960},
      {"MEMSS4_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1040},
      {"MEMSS5_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1120},
      {"MEMSS6_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1200},
      {"MEMSS7_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1280},
      {"MEMSS8_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1360},
      {"MEMSS9_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1440},
      {"MEMSS10_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1520},
      {"MEMSS11_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1600},
      {"MEMSS12_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1680},
      {"MEMSS13_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1760},
      {"MEMSS14_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1840},
      {"MEMSS15_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 1920},
      {"MEMSS16_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 2000},
      {"MEMSS17_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 2080},
      {"MEMSS18_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 2160},
      {"MEMSS19_PERF_CTR_MB1_CFI_NUM_WRITE_REQ", 2240},
      {"NUM_OF_MEM_CHANNEL", 3660}}}};

static ze_result_t getErrorCode(ze_result_t result) {
    if (result == ZE_RESULT_ERROR_NOT_AVAILABLE) {
        result = ZE_RESULT_ERROR_UNSUPPORTED_FEATURE;
    }
    return result;
}

template <>
const std::map<std::string, std::map<std::string, uint64_t>> *SysmanProductHelperHw<gfxProduct>::getGuidToKeyOffsetMap() {
    return &guidToKeyOffsetMap;
}

static ze_result_t buildKeyOffsetMapFromTelemNodes(const std::string &rootPath,
                                                   std::map<std::string, uint64_t> &keyOffsetMap,
                                                   std::unordered_map<std::string, std::string> &keyTelemInfoMap) {
    std::map<uint32_t, std::string> telemNodes;
    NEO::PmtUtil::getTelemNodesInPciPath(std::string_view(rootPath), telemNodes);
    if (telemNodes.empty()) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): No telemetry nodes found in PCI path, returning error 0x%x>\n", __func__, ZE_RESULT_ERROR_UNSUPPORTED_FEATURE);
        return ZE_RESULT_ERROR_UNSUPPORTED_FEATURE;
    }

    keyOffsetMap.clear();
    keyTelemInfoMap.clear();

    for (const auto &it : telemNodes) {
        std::string telemNodeDir = it.second;

        std::array<char, NEO::PmtUtil::guidStringSize> guidString = {};
        if (!NEO::PmtUtil::readGuid(telemNodeDir, guidString)) {
            continue;
        }

        auto keyOffsetMapIterator = guidToKeyOffsetMap.find(guidString.data());
        if (keyOffsetMapIterator == guidToKeyOffsetMap.end()) {
            continue;
        }

        const auto &tempKeyOffsetMap = keyOffsetMapIterator->second;
        for (const auto &[key, value] : tempKeyOffsetMap) {
            keyOffsetMap[key] = value;
            keyTelemInfoMap[key] = telemNodeDir;
        }
    }

    if (keyOffsetMap.empty()) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to find KeyOffsetMap, returning error 0x%x>\n", __func__, ZE_RESULT_ERROR_UNSUPPORTED_FEATURE);
        return ZE_RESULT_ERROR_UNSUPPORTED_FEATURE;
    }

    return ZE_RESULT_SUCCESS;
}

template <>
bool SysmanProductHelperHw<gfxProduct>::isFrequencySetRangeSupported() {
    return false;
}

template <>
bool SysmanProductHelperHw<gfxProduct>::isPerfFactorSupported() {
    return false;
}

template <>
bool SysmanProductHelperHw<gfxProduct>::isStandbySupported(SysmanKmdInterface *pSysmanKmdInterface) {
    return false;
}

template <>
int32_t SysmanProductHelperHw<gfxProduct>::getPowerMinLimit(const int32_t &defaultLimit) {
    return static_cast<int32_t>(defaultLimit * minPowerLimitFactor);
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getLimitsExp(SysmanKmdInterface *pSysmanKmdInterface, SysFsAccessInterface *pSysfsAccess, const std::map<std::string, std::pair<std::string, bool>> &powerLimitFiles, uint32_t *pLimit) {
    ze_result_t result = ZE_RESULT_SUCCESS;
    uint64_t powerLimit = 0;

    const auto &[sustainedPowerLimitFile, sustainedPowerLimitFileExists] = powerLimitFiles.at("sustainedLimitFile");
    const auto &[burstPowerLimitFile, burstPowerLimitFileExists] = powerLimitFiles.at("burstLimitFile");

    // Return PL1 if enabled, otherwise return PL2
    if (sustainedPowerLimitFileExists) {
        result = pSysfsAccess->read(sustainedPowerLimitFile, powerLimit);
        if (ZE_RESULT_SUCCESS != result) {
            PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): SysfsAccess->read() failed to read %s and returning error:0x%x \n", __FUNCTION__, sustainedPowerLimitFile.c_str(), getErrorCode(result));
            return getErrorCode(result);
        }
    } else if (burstPowerLimitFileExists) {
        result = pSysfsAccess->read(burstPowerLimitFile, powerLimit);
        if (ZE_RESULT_SUCCESS != result) {
            PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): SysfsAccess->read() failed to read %s and returning error:0x%x \n", __FUNCTION__, burstPowerLimitFile.c_str(), getErrorCode(result));
            return getErrorCode(result);
        }
    } else {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): No power limit files exist for given power domain , returning unsupported feature\n", __FUNCTION__);
        return ZE_RESULT_ERROR_DEPENDENCY_UNAVAILABLE;
    }

    pSysmanKmdInterface->convertSysfsValueUnit(SysfsValueUnit::milli, SysfsValueUnit::micro, powerLimit, powerLimit);
    *pLimit = static_cast<uint32_t>(powerLimit);
    return ZE_RESULT_SUCCESS;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::setLimitsExp(SysmanKmdInterface *pSysmanKmdInterface, SysFsAccessInterface *pSysfsAccess, const std::map<std::string, std::pair<std::string, bool>> &powerLimitFiles, zes_power_domain_t powerDomain, const uint32_t limit) {
    ze_result_t result = ZE_RESULT_SUCCESS;
    uint64_t val = static_cast<uint64_t>(limit);
    uint64_t convertedVal = 0;
    bool anyLimitSet = false;

    const auto &[sustainedPowerLimitFile, sustainedPowerLimitFileExists] = powerLimitFiles.at("sustainedLimitFile");
    const auto &[burstPowerLimitFile, burstPowerLimitFileExists] = powerLimitFiles.at("burstLimitFile");
    const auto &[criticalPowerLimitFile, criticalPowerLimitFileExists] = powerLimitFiles.at("criticalLimitFile");

    if (powerDomain == ZES_POWER_DOMAIN_CARD) {
        // Card domain: Apply to PL1 and PL2 if enabled, Apply x2 to PsysCRIT if enabled
        if (sustainedPowerLimitFileExists) {
            convertedVal = val;
            pSysmanKmdInterface->convertSysfsValueUnit(pSysmanKmdInterface->getNativeUnit(SysfsName::sysfsNameCardSustainedPowerLimit), SysfsValueUnit::milli, convertedVal, convertedVal);
            result = pSysfsAccess->write(sustainedPowerLimitFile, convertedVal);
            if (ZE_RESULT_SUCCESS != result) {
                PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): SysfsAccess->write() failed to write into %s and returning error:0x%x \n", __FUNCTION__, sustainedPowerLimitFile.c_str(), getErrorCode(result));
                return getErrorCode(result);
            }
            anyLimitSet = true;
        }

        if (burstPowerLimitFileExists) {
            convertedVal = val;
            pSysmanKmdInterface->convertSysfsValueUnit(pSysmanKmdInterface->getNativeUnit(SysfsName::sysfsNameCardBurstPowerLimit), SysfsValueUnit::milli, convertedVal, convertedVal);
            result = pSysfsAccess->write(burstPowerLimitFile, convertedVal);
            if (ZE_RESULT_SUCCESS != result) {
                PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): SysfsAccess->write() failed to write into %s and returning error:0x%x \n", __FUNCTION__, burstPowerLimitFile.c_str(), getErrorCode(result));
                return getErrorCode(result);
            }
            anyLimitSet = true;
        }

        if (criticalPowerLimitFileExists) {
            convertedVal = val * criticalLimitMultiplyFactor;
            pSysmanKmdInterface->convertSysfsValueUnit(pSysmanKmdInterface->getNativeUnit(SysfsName::sysfsNameCardCriticalPowerLimit), SysfsValueUnit::milli, convertedVal, convertedVal);
            result = pSysfsAccess->write(criticalPowerLimitFile, convertedVal);
            if (ZE_RESULT_SUCCESS != result) {
                PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): SysfsAccess->write() failed to write into %s and returning error:0x%x \n", __FUNCTION__, criticalPowerLimitFile.c_str(), getErrorCode(result));
                return getErrorCode(result);
            }
            anyLimitSet = true;
        }
    } else if (powerDomain == ZES_POWER_DOMAIN_PACKAGE) {
        // Package domain: Apply to PL1 and PL2 if enabled
        if (sustainedPowerLimitFileExists) {
            convertedVal = val;
            pSysmanKmdInterface->convertSysfsValueUnit(pSysmanKmdInterface->getNativeUnit(SysfsName::sysfsNamePackageSustainedPowerLimit), SysfsValueUnit::milli, convertedVal, convertedVal);
            result = pSysfsAccess->write(sustainedPowerLimitFile, convertedVal);
            if (ZE_RESULT_SUCCESS != result) {
                PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): SysfsAccess->write() failed to write into %s and returning error:0x%x \n", __FUNCTION__, sustainedPowerLimitFile.c_str(), getErrorCode(result));
                return getErrorCode(result);
            }
            anyLimitSet = true;
        }

        if (burstPowerLimitFileExists) {
            convertedVal = val;
            pSysmanKmdInterface->convertSysfsValueUnit(pSysmanKmdInterface->getNativeUnit(SysfsName::sysfsNamePackageBurstPowerLimit), SysfsValueUnit::milli, convertedVal, convertedVal);
            result = pSysfsAccess->write(burstPowerLimitFile, convertedVal);
            if (ZE_RESULT_SUCCESS != result) {
                PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): SysfsAccess->write() failed to write into %s and returning error:0x%x \n", __FUNCTION__, burstPowerLimitFile.c_str(), getErrorCode(result));
                return getErrorCode(result);
            }
            anyLimitSet = true;
        }
    } else {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Power Limits for given domain do not exist , returning error:0x%x\n", __FUNCTION__, ZE_RESULT_ERROR_DEPENDENCY_UNAVAILABLE);
        return ZE_RESULT_ERROR_DEPENDENCY_UNAVAILABLE;
    }

    if (!anyLimitSet) {
        return ZE_RESULT_ERROR_DEPENDENCY_UNAVAILABLE;
    }

    return ZE_RESULT_SUCCESS;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getPowerEnergyCounter(zes_power_energy_counter_t *pEnergy, LinuxSysmanImp *pLinuxSysmanImp, zes_power_domain_t powerDomain, uint32_t subdeviceId) {
    const std::unordered_map<zes_power_domain_t, std::string> powerDomainToKeyMap = {
        {ZES_POWER_DOMAIN_PACKAGE, "ACCUM_PACKAGE_ENERGY"},
        {ZES_POWER_DOMAIN_CARD, "ACCUM_PSYS_ENERGY"},
        {ZES_POWER_DOMAIN_GPU, "VCCGT_ENERGY_ACCUMULATOR"},
        {ZES_POWER_DOMAIN_MEMORY, "VCCDDRQ_ENERGY_ACCUMULATOR"}};

    auto powerDomainToKeyMapIter = powerDomainToKeyMap.find(powerDomain);
    if (powerDomainToKeyMapIter == powerDomainToKeyMap.end()) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Power domain not supported for Energy counter, returning error:0x%x \n", __FUNCTION__, ZE_RESULT_ERROR_UNSUPPORTED_FEATURE);
        return ZE_RESULT_ERROR_UNSUPPORTED_FEATURE;
    }

    std::map<std::string, uint64_t> keyOffsetMap;
    std::unordered_map<std::string, std::string> keyTelemInfoMap;
    std::string &rootPath = pLinuxSysmanImp->getPciRootPath();
    ze_result_t result = buildKeyOffsetMapFromTelemNodes(rootPath, keyOffsetMap, keyTelemInfoMap);
    if (result != ZE_RESULT_SUCCESS) {
        return result;
    }

    uint32_t energyCounter = 0;
    std::string key = powerDomainToKeyMapIter->second;
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[key], key, 0, energyCounter)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read Energy counter from Telemetry, returning error:0x%x \n", __FUNCTION__, ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    // Energy Counter calculation
    double energyInJoules = 0.0;
    if (powerDomain == ZES_POWER_DOMAIN_MEMORY) {
        energyInJoules = convertU18p14((energyCounter >> 16) & 0xFFFF) + convertU18p14(energyCounter & 0xFFFF);
    } else {
        energyInJoules = convertU18p14(energyCounter);
    }

    // Convert Energy in Joules to MicroJoules
    pEnergy->energy = static_cast<uint64_t>(energyInJoules * microFactor);

    // Timestamp calculation
    uint32_t timestampValue = 0;
    key = "XTAL_COUNT";
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[key], key, 0, timestampValue)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read Xtal clock from Telemetry, returning error:0x%x \n", __FUNCTION__, ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    uint32_t frequency = 0;
    key = "XTAL_CLK_FREQUENCY";
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[key], key, 0, frequency)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read Xtal clock frequency from Telemetry, returning error:0x%x \n", __FUNCTION__, ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    double timestamp = timestampValue / indexToXtalClockFrequencyMap[frequency & 0x2];
    pEnergy->timestamp = static_cast<uint64_t>(timestamp);

    return ZE_RESULT_SUCCESS;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getPowerUsageExp(LinuxSysmanImp *pLinuxSysmanImp, zes_power_domain_t powerDomain, uint32_t *pInstantPower, uint32_t *pAveragePower) {
    std::map<std::string, uint64_t> keyOffsetMap;
    std::unordered_map<std::string, std::string> keyTelemInfoMap;
    std::string &rootPath = pLinuxSysmanImp->getPciRootPath();
    ze_result_t result = buildKeyOffsetMapFromTelemNodes(rootPath, keyOffsetMap, keyTelemInfoMap);
    if (result != ZE_RESULT_SUCCESS) {
        return result;
    }

    uint64_t instantaneousPowerValue = 0;
    std::string key = "INSTANTANEOUS_POWER_CONTAINER"; // 64-bit container with Instantaneous power values at different bit offsets
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[key], key, 0, instantaneousPowerValue)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read Instantaneous Power from Telemetry, returning error:0x%x \n", __FUNCTION__, ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    uint64_t averagePowerValue = 0;
    key = "AVERAGE_POWER_CONTAINER"; // 64-bit container with Average power values at different bit offsets
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[key], key, 0, averagePowerValue)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read Average Power from Telemetry, returning error:0x%x \n", __FUNCTION__, ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    // Power Values read from PMT are in U13.3 format (13 integer bits + 3 fractional bits = 16 bits) and in Watts
    switch (powerDomain) {
    case ZES_POWER_DOMAIN_CARD:
        // bits [32:47] - INSTANTANEOUS_PSYSGPU_POWER
        *pInstantPower = static_cast<uint32_t>(convertU13p3((instantaneousPowerValue >> 32) & 0xFFFF) * milliFactor);
        // bits [32:47] - SUSTAINED_PACKAGE_POWER
        *pAveragePower = static_cast<uint32_t>(convertU13p3((averagePowerValue >> 32) & 0xFFFF) * milliFactor);
        break;
    case ZES_POWER_DOMAIN_PACKAGE:
        // bits [0:15] - INSTANTANEOUS_PACKAGE_POWER
        *pInstantPower = static_cast<uint32_t>(convertU13p3(instantaneousPowerValue & 0xFFFF) * milliFactor);
        // bits [0:15] - SUSTAINED_CARD_POWER
        *pAveragePower = static_cast<uint32_t>(convertU13p3(averagePowerValue & 0xFFFF) * milliFactor);
        break;
    case ZES_POWER_DOMAIN_MEMORY: {
        // bits [16:31] INSTANTANEOUS_VRAM_VCCDRQX_POWER + bits [48:63] INSTANTANEOUS_VRAM_VCCDDRQ_POWER
        double instVccdrqx = convertU13p3((instantaneousPowerValue >> 16) & 0xFFFF);
        double instVccddrq = convertU13p3((instantaneousPowerValue >> 48) & 0xFFFF);
        double instTotalWatts = instVccdrqx + instVccddrq;
        double instMilliWatts = instTotalWatts * milliFactor;
        *pInstantPower = static_cast<uint32_t>(instMilliWatts);

        // bits [16:31] SUSTAINED_POWER_VCCDDRQX + bits [48:63] SUSTAINED_POWER_VCCDDRQ
        double avgVccdrqx = convertU13p3((averagePowerValue >> 16) & 0xFFFF);
        double avgVccddrq = convertU13p3((averagePowerValue >> 48) & 0xFFFF);
        double avgTotalWatts = avgVccdrqx + avgVccddrq;
        double avgMilliWatts = avgTotalWatts * milliFactor;
        *pAveragePower = static_cast<uint32_t>(avgMilliWatts);

        break;
    }
    default:
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Unsupported power domain, returning error:0x%x \n", __FUNCTION__, ZE_RESULT_ERROR_UNSUPPORTED_FEATURE);
        return ZE_RESULT_ERROR_UNSUPPORTED_FEATURE;
    }

    return ZE_RESULT_SUCCESS;
}

template <>
RasInterfaceType SysmanProductHelperHw<gfxProduct>::getGtRasUtilInterface() {
    return RasInterfaceType::netlink;
}

template <>
RasInterfaceType SysmanProductHelperHw<gfxProduct>::getHbmRasUtilInterface() {
    return RasInterfaceType::netlink;
}

template <>
bool SysmanProductHelperHw<gfxProduct>::isUpstreamPortConnected() {
    return true;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getPciProperties(zes_pci_properties_t *pProperties) {
    pProperties->haveBandwidthCounters = true;
    pProperties->havePacketCounters = true;
    pProperties->haveReplayCounters = false;
    return ZE_RESULT_SUCCESS;
}

template <>
int32_t SysmanProductHelperHw<gfxProduct>::maxPcieGenSupported() {
    constexpr int32_t maxPcieGenSupported = 5;
    return maxPcieGenSupported;
}

static ze_result_t getPciStatsValues(zes_pci_stats_t *pStats, std::map<std::string, uint64_t> &keyOffsetMap, const std::string &telemNodeDir) {
    uint64_t rxCounter = 0;
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, telemNodeDir, "PCIE_RECEIVE_BYTES", 0, rxCounter)) {
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    uint64_t txCounter = 0;
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, telemNodeDir, "PCIE_TRANSMIT_BYTES", 0, txCounter)) {
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    uint64_t rxPacketCounter = 0;
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, telemNodeDir, "PCIE_RECEIVE_PACKETS", 0, rxPacketCounter)) {
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    uint64_t txPacketCounter = 0;
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, telemNodeDir, "PCIE_TRANSMIT_PACKETS", 0, txPacketCounter)) {
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    uint64_t timeStamp = 0;
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, telemNodeDir, "LPDDR_TELEM_CAPTURE_TIMESTAMP", 0, timeStamp)) {
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    pStats->speed.gen = -1;
    pStats->speed.width = -1;
    pStats->speed.maxBandwidth = -1;
    pStats->replayCounter = 0;
    pStats->rxCounter = rxCounter;
    pStats->txCounter = txCounter;
    pStats->packetCounter = rxPacketCounter + txPacketCounter;
    pStats->timestamp = timeStamp * milliSecsToMicroSecs;

    return ZE_RESULT_SUCCESS;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getPciStats(zes_pci_stats_t *pStats, LinuxSysmanImp *pLinuxSysmanImp) {
    std::string &rootPath = pLinuxSysmanImp->getPciRootPath();
    std::map<uint32_t, std::string> telemNodes;
    NEO::PmtUtil::getTelemNodesInPciPath(std::string_view(rootPath), telemNodes);
    if (telemNodes.empty()) {
        return ZE_RESULT_ERROR_UNSUPPORTED_FEATURE;
    }

    ze_result_t result = ZE_RESULT_ERROR_UNSUPPORTED_FEATURE;
    for (auto &it : telemNodes) {
        std::string telemNodeDir = it.second;

        std::array<char, NEO::PmtUtil::guidStringSize> guidString = {};
        if (!NEO::PmtUtil::readGuid(telemNodeDir, guidString)) {
            continue;
        }

        auto keyOffsetMapIterator = guidToKeyOffsetMap.find(guidString.data());
        if (keyOffsetMapIterator == guidToKeyOffsetMap.end()) {
            continue;
        }

        result = getPciStatsValues(pStats, keyOffsetMapIterator->second, telemNodeDir);
        if (result == ZE_RESULT_SUCCESS) {
            break;
        }
    }

    return result;
};

static zes_freq_throttle_reason_flags_t getAggregatedThrottleReasons(const zes_intel_freq_throttle_detailed_reason_exp_flags_t &pDetailedThrottleReasons) {

    const zes_freq_throttle_reason_flags_t powerFlags =
        static_cast<zes_freq_throttle_reason_flags_t>(ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_CARD_PL1 |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_CARD_PL2 |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_CARD_PL4 |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_PACKAGE_PL1 |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_PACKAGE_PL2 |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_PACKAGE_PL4 |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_FAST_VMODE |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_ICCMAX);

    const zes_freq_throttle_reason_flags_t thermalFlags =
        static_cast<zes_freq_throttle_reason_flags_t>(ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_MEMORY |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_PROCHOT |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_RATL |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_SOC |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_SOC_AVG |
                                                      ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_VR);

    const zes_freq_throttle_reason_flags_t voltageFlags =
        static_cast<zes_freq_throttle_reason_flags_t>(ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_VOLTAGE_P0_FREQ);

    zes_freq_throttle_reason_flags_t aggregatedReasons = 0u;

    if (pDetailedThrottleReasons & powerFlags) {
        aggregatedReasons |= ZES_FREQ_THROTTLE_REASON_FLAG_POWER;
    }
    if (pDetailedThrottleReasons & thermalFlags) {
        aggregatedReasons |= ZES_FREQ_THROTTLE_REASON_FLAG_THERMAL;
    }
    if (pDetailedThrottleReasons & voltageFlags) {
        aggregatedReasons |= ZES_FREQ_THROTTLE_REASON_FLAG_VOLTAGE;
    }

    return aggregatedReasons;
}

static ze_result_t getDetailedThrottleReasons(SysmanKmdInterface *pSysmanKmdInterface, SysFsAccessInterface *pSysfsAccess, uint32_t subdeviceId, zes_intel_freq_throttle_detailed_reason_exp_flags_t &detailedThrottleReasons) {

    const std::string baseDir = pSysmanKmdInterface->getBasePath(subdeviceId);
    bool baseDirectoryExists = pSysfsAccess->directoryExists(baseDir);

    uint32_t reasonStatusVal = 0;
    std::string throttleReasonStatusFile = pSysmanKmdInterface->getSysfsFilePath(SysfsName::sysfsNameThrottleReasonStatus, subdeviceId, baseDirectoryExists);
    auto result = pSysfsAccess->read(throttleReasonStatusFile, reasonStatusVal);

    if (ZE_RESULT_SUCCESS != result || reasonStatusVal == 0) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read Throttle reason status file %s or no throttle reasons are active, returning error 0x%x>\n", __func__, throttleReasonStatusFile.c_str(), result);
        return ZE_RESULT_ERROR_DEPENDENCY_UNAVAILABLE;
    }

    static constexpr std::pair<const char *, zes_intel_freq_throttle_detailed_reason_exp_flag_t> throttleReasonMap[] = {
        {"reason_psys_pl1", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_CARD_PL1},
        {"reason_psys_pl2", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_CARD_PL2},
        {"reason_psys_crit", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_CARD_PL4},
        {"reason_pl1", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_PACKAGE_PL1},
        {"reason_pl2", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_PACKAGE_PL2},
        {"reason_pl4", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_PACKAGE_PL4},
        {"reason_fastvmode", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_FAST_VMODE},
        {"reason_iccmax", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_POWER_ICCMAX},
        {"reason_soc_thermal", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_SOC},
        {"reason_soc_avg_thermal", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_SOC_AVG},
        {"reason_memory_thermal", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_MEMORY},
        {"reason_vr_thermal", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_VR},
        {"reason_ratl", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_RATL},
        {"reason_prochot", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_THERMAL_PROCHOT},
        {"reason_p0_freq", ZES_INTEL_FREQ_THROTTLE_DETAILED_REASON_EXP_FLAG_VOLTAGE_P0_FREQ}};

    const std::string throttleReasonPath("freq0/throttle/");
    uint32_t detailedThrottleReasonVal = 0u;
    for (const auto &reasonPair : throttleReasonMap) {
        const std::string filePath = baseDirectoryExists ? baseDir + throttleReasonPath + reasonPair.first : std::string("");
        detailedThrottleReasonVal = 0u;
        if ((pSysfsAccess->read(filePath, detailedThrottleReasonVal) == ZE_RESULT_SUCCESS) && detailedThrottleReasonVal) {
            detailedThrottleReasons |= reasonPair.second;
        }
    }

    return ZE_RESULT_SUCCESS;
}

template <>
zes_freq_throttle_reason_flags_t SysmanProductHelperHw<gfxProduct>::getThrottleReasons(SysmanKmdInterface *pSysmanKmdInterface, SysFsAccessInterface *pSysfsAccess, uint32_t subdeviceId, void *pNext) {
    zes_intel_freq_throttle_detailed_reason_exp_flags_t detailedThrottleReasons = 0u;
    ze_result_t result = getDetailedThrottleReasons(pSysmanKmdInterface, pSysfsAccess, subdeviceId, detailedThrottleReasons);
    if (result != ZE_RESULT_SUCCESS) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stdout, "Info@ %s(): Failed to get detailed throttle reasons, returning 0 reasons\n", __func__);
        return static_cast<zes_freq_throttle_reason_flags_t>(0);
    }

    zes_freq_throttle_reason_flags_t aggregatedReasons = getAggregatedThrottleReasons(detailedThrottleReasons);
    void *pCurrent = pNext;
    while (pCurrent) {
        auto pExpThrottleReason = reinterpret_cast<zes_base_properties_t *>(pCurrent);
        if (pExpThrottleReason->stype == ZES_INTEL_STRUCTURE_TYPE_FREQUENCY_THROTTLE_DETAILED_REASON_EXP) {
            auto pDetailedThrottleReason = reinterpret_cast<zes_intel_freq_throttle_detailed_reason_exp_t *>(pExpThrottleReason);
            pDetailedThrottleReason->detailedReasons = detailedThrottleReasons;
            break;
        }
        pCurrent = pExpThrottleReason->pNext;
    }

    // Set Utilization Limited reason flag if none of the detailed reasons are active
    if (detailedThrottleReasons == 0u) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stdout, "Info@ %s(): No detailed throttle reasons are active, setting Utilization Limited reason flag\n", __func__);
        aggregatedReasons |= static_cast<zes_freq_throttle_reason_flags_t>(ZES_INTEL_FREQ_THROTTLE_REASON_EXP_FLAG_UTILIZATION_LIMITED);
    }

    return aggregatedReasons;
}

template <>
void SysmanProductHelperHw<gfxProduct>::getSupportedSensors(std::map<zes_temp_sensors_t, uint32_t> &supportedSensorTypeMap) {
    supportedSensorTypeMap[ZES_TEMP_SENSORS_GLOBAL] = 1;
    supportedSensorTypeMap[ZES_TEMP_SENSORS_GPU] = 1;
    supportedSensorTypeMap[ZES_TEMP_SENSORS_MEMORY] = 1;
    supportedSensorTypeMap[ZES_TEMP_SENSORS_VOLTAGE_REGULATOR] = 4;
    supportedSensorTypeMap[ZES_TEMP_SENSORS_GPU_BOARD] = 2;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getVoltageRegulatorTemperature(LinuxSysmanImp *pLinuxSysmanImp, double *pTemperature, uint32_t subdeviceId, uint32_t sensorIndex) {
    std::string &rootPath = pLinuxSysmanImp->getPciRootPath();
    std::map<std::string, uint64_t> keyOffsetMap;
    std::unordered_map<std::string, std::string> keyTelemInfoMap;

    ze_result_t result = PlatformMonitoringTech::buildKeyOffsetMapFromTelemNodes(guidToKeyOffsetMap, rootPath, keyOffsetMap, keyTelemInfoMap);
    if (result != ZE_RESULT_SUCCESS) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to build key offset map from telemetry nodes, returning error:0x%x \n", __FUNCTION__, result);
        return result;
    }

    // Build the key name based on sensor index
    std::string key = "VR_TEMPERATURE_" + std::to_string(sensorIndex);

    uint32_t vrTemperature = 0;
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[key], key, 0, vrTemperature)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read VR temperature value for key: %s, returning error:0x%x \n", __FUNCTION__, key.c_str(), ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    switch (sensorIndex) {
    case 0:
        vrTemperature /= 10; // VR_TEMPERATURE_0 is reported in deci-degree celsius
        break;
    case 1:
    case 2:
    case 3:
        vrTemperature = vrTemperature & 0xFF; // VR_TEMPERATURE_1/2/3 is reported in U8.0 format
        break;
    default:
        DEBUG_BREAK_IF(true);
        return ZE_RESULT_ERROR_UNKNOWN;
    }

    *pTemperature = static_cast<double>(vrTemperature);
    return ZE_RESULT_SUCCESS;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getGpuBoardTemperature(LinuxSysmanImp *pLinuxSysmanImp, double *pTemperature, uint32_t subdeviceId, uint32_t sensorIndex) {
    std::string &rootPath = pLinuxSysmanImp->getPciRootPath();
    std::map<std::string, uint64_t> keyOffsetMap;
    std::unordered_map<std::string, std::string> keyTelemInfoMap;

    ze_result_t result = PlatformMonitoringTech::buildKeyOffsetMapFromTelemNodes(guidToKeyOffsetMap, rootPath, keyOffsetMap, keyTelemInfoMap);
    if (result != ZE_RESULT_SUCCESS) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to build key offset map from telemetry nodes, returning error:0x%x \n", __FUNCTION__, result);
        return result;
    }

    // Both GPU board temperature sensors share the same register
    std::string key = "GPU_BOARD_TEMPERATURE";

    uint32_t gpuBoardTemperature = 0;
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[key], key, 0, gpuBoardTemperature)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read GPU board temperature value for key: %s, returning error:0x%x \n", __FUNCTION__, key.c_str(), ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    switch (sensorIndex) {
    case 0:
        // Index 0: lower 16 bits, temperature in U8.0 format (bits 0-7)
        gpuBoardTemperature = gpuBoardTemperature & 0xFF;
        break;
    case 1:
        // Index 1: upper 16 bits, temperature in U8.0 format (bits 16-23)
        gpuBoardTemperature = (gpuBoardTemperature >> 16) & 0xFF;
        break;
    default:
        DEBUG_BREAK_IF(true);
        return ZE_RESULT_ERROR_UNKNOWN;
    }

    *pTemperature = static_cast<double>(gpuBoardTemperature);
    return ZE_RESULT_SUCCESS;
}

template <>
bool SysmanProductHelperHw<gfxProduct>::isMemoryMaxTemperatureSupported() {
    return true;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getGpuMaxTemperature(LinuxSysmanImp *pLinuxSysmanImp, double *pTemperature, uint32_t subdeviceId) {

    std::string &rootPath = pLinuxSysmanImp->getPciRootPath();
    std::map<std::string, uint64_t> keyOffsetMap;
    std::unordered_map<std::string, std::string> keyTelemInfoMap;

    ze_result_t result = PlatformMonitoringTech::buildKeyOffsetMapFromTelemNodes(guidToKeyOffsetMap, rootPath, keyOffsetMap, keyTelemInfoMap);
    if (result != ZE_RESULT_SUCCESS) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to build key offset map from telemetry nodes, returning error:0x%x \n", __FUNCTION__, result);
        return result;
    }

    uint32_t gpuMaxTemperature = 0;
    uint64_t telemOffset = 0;
    std::string key("SOC_TOPDIE_TEMPERATURE");
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[key], key, telemOffset, gpuMaxTemperature)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read value for key: %s, returning error:0x%x \n", __FUNCTION__, key.c_str(), ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }
    *pTemperature = static_cast<double>(gpuMaxTemperature);

    return ZE_RESULT_SUCCESS;
}

static ze_result_t getMemoryBandwidthCounterValues(const std::map<std::string, uint64_t> &keyOffsetMap, std::unordered_map<std::string, std::string> &keyTelemInfoMap,
                                                   zes_mem_bandwidth_t *pBandwidth) {

    uint64_t readCounter = 0;
    uint64_t writeCounter = 0;
    uint64_t telemOffset = 0;

    for (uint32_t i = 0; i < memoryMsuCount; i++) {
        uint64_t msuReadCounter = 0;
        uint64_t msuWriteCounter = 0;

        for (uint32_t mb = 0; mb < memoryBridgeCount; mb++) {
            std::string mbSuffix = "MB" + std::to_string(mb);

            std::string readKey = "MEMSS" + std::to_string(i) + "_PERF_CTR_" + mbSuffix + "_CFI_NUM_READ_REQ";
            uint64_t readCounterValue = 0;
            if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[readKey], readKey, telemOffset, readCounterValue)) {
                PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read value for key: %s, returning error:0x%x \n", __FUNCTION__, readKey.c_str(), ZE_RESULT_ERROR_NOT_AVAILABLE);
                return ZE_RESULT_ERROR_NOT_AVAILABLE;
            }
            msuReadCounter += readCounterValue;

            std::string writeKey = "MEMSS" + std::to_string(i) + "_PERF_CTR_" + mbSuffix + "_CFI_NUM_WRITE_REQ";
            uint64_t writeCounterValue = 0;
            if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[writeKey], writeKey, telemOffset, writeCounterValue)) {
                PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read value for key: %s, returning error:0x%x \n", __FUNCTION__, writeKey.c_str(), ZE_RESULT_ERROR_NOT_AVAILABLE);
                return ZE_RESULT_ERROR_NOT_AVAILABLE;
            }
            msuWriteCounter += writeCounterValue;
        }

        readCounter += msuReadCounter;
        writeCounter += msuWriteCounter;
    }

    pBandwidth->readCounter = readCounter * transactionSize;
    pBandwidth->writeCounter = writeCounter * transactionSize;
    return ZE_RESULT_SUCCESS;
}

static ze_result_t getMemoryMaxBandwidth(const std::map<std::string, uint64_t> &keyOffsetMap, std::unordered_map<std::string, std::string> &keyTelemInfoMap,
                                         zes_mem_bandwidth_t *pBandwidth) {

    uint64_t telemOffset = 0;
    uint32_t maxBandwidth = 0;
    std::string key = "VRAM_BANDWIDTH";
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[key], key, telemOffset, maxBandwidth)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read value for key: %s, returning error:0x%x \n", __FUNCTION__, key.c_str(), ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    maxBandwidth = maxBandwidth >> 16;
    pBandwidth->maxBandwidth = static_cast<uint64_t>(maxBandwidth) * mbpsToBytesPerSec;

    return ZE_RESULT_SUCCESS;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getMemoryMaxTemperature(LinuxSysmanImp *pLinuxSysmanImp, double *pTemperature, uint32_t subdeviceId) {

    std::string &rootPath = pLinuxSysmanImp->getPciRootPath();
    std::map<std::string, uint64_t> keyOffsetMap;
    std::unordered_map<std::string, std::string> keyTelemInfoMap;

    ze_result_t result = PlatformMonitoringTech::buildKeyOffsetMapFromTelemNodes(guidToKeyOffsetMap, rootPath, keyOffsetMap, keyTelemInfoMap);
    if (result != ZE_RESULT_SUCCESS) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to build key offset map from telemetry nodes, returning error:0x%x \n", __FUNCTION__, result);
        return result;
    }

    uint64_t telemOffset = 0;
    uint32_t memoryMaxTemperature = 0;
    std::string key("VRAM_TEMPERATURE");
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[key], key, telemOffset, memoryMaxTemperature)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read value for key: %s, returning error:0x%x \n", __FUNCTION__, key.c_str(), ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }
    memoryMaxTemperature &= 0xFFu; // Extract least significant 8 bits
    *pTemperature = static_cast<double>(memoryMaxTemperature);

    return ZE_RESULT_SUCCESS;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getMemoryBandwidth(zes_mem_bandwidth_t *pBandwidth, LinuxSysmanImp *pLinuxSysmanImp, uint32_t subdeviceId) {

    std::string &rootPath = pLinuxSysmanImp->getPciRootPath();
    std::map<std::string, uint64_t> keyOffsetMap;
    std::unordered_map<std::string, std::string> keyTelemInfoMap;

    ze_result_t result = PlatformMonitoringTech::buildKeyOffsetMapFromTelemNodes(guidToKeyOffsetMap, rootPath, keyOffsetMap, keyTelemInfoMap);
    if (result != ZE_RESULT_SUCCESS) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to build key offset map from telemetry nodes, returning error:0x%x \n", __FUNCTION__, result);
        return result;
    }

    if (ZE_RESULT_SUCCESS != getMemoryBandwidthCounterValues(keyOffsetMap, keyTelemInfoMap, pBandwidth)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to get the Read and Write Counter Values, returning error 0x%x>\n", __func__, ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    if (ZE_RESULT_SUCCESS != getMemoryMaxBandwidth(keyOffsetMap, keyTelemInfoMap, pBandwidth)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to get the Max Bandwidth Value, returning error 0x%x>\n", __func__, ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    pBandwidth->timestamp = SysmanDevice::getSysmanTimestamp();

    return ZE_RESULT_SUCCESS;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getGlobalMaxTemperature(LinuxSysmanImp *pLinuxSysmanImp, double *pTemperature, uint32_t subdeviceId) {
    double gpuMaxTemperature = 0;
    ze_result_t result = this->getGpuMaxTemperature(pLinuxSysmanImp, &gpuMaxTemperature, subdeviceId);
    if (result != ZE_RESULT_SUCCESS) {
        return result;
    }

    double memoryMaxTemperature = 0;
    result = this->getMemoryMaxTemperature(pLinuxSysmanImp, &memoryMaxTemperature, subdeviceId);
    if (result != ZE_RESULT_SUCCESS) {
        return result;
    }

    double vrMaxTemperature = 0;
    std::map<zes_temp_sensors_t, uint32_t> supportedSensorTypeMap;
    getSupportedSensors(supportedSensorTypeMap);
    uint32_t vrSensorCount = supportedSensorTypeMap.at(ZES_TEMP_SENSORS_VOLTAGE_REGULATOR);
    for (uint32_t i = 0; i < vrSensorCount; i++) {
        double currentVrTemperature = 0;
        result = this->getVoltageRegulatorTemperature(pLinuxSysmanImp, &currentVrTemperature, subdeviceId, i);
        if (result != ZE_RESULT_SUCCESS) {
            return result;
        }
        vrMaxTemperature = std::max(vrMaxTemperature, currentVrTemperature);
    }

    double gpuBoardMaxTemperature = 0;
    uint32_t gpuBoardSensorCount = supportedSensorTypeMap.at(ZES_TEMP_SENSORS_GPU_BOARD);
    for (uint32_t i = 0; i < gpuBoardSensorCount; i++) {
        double currentGpuBoardTemperature = 0;
        result = this->getGpuBoardTemperature(pLinuxSysmanImp, &currentGpuBoardTemperature, subdeviceId, i);
        if (result != ZE_RESULT_SUCCESS) {
            return result;
        }
        gpuBoardMaxTemperature = std::max(gpuBoardMaxTemperature, currentGpuBoardTemperature);
    }
    *pTemperature = std::max({gpuMaxTemperature, memoryMaxTemperature, vrMaxTemperature, gpuBoardMaxTemperature});
    return result;
}

template <>
ze_result_t SysmanProductHelperHw<gfxProduct>::getMemoryProperties(zes_mem_properties_t *pProperties, LinuxSysmanImp *pLinuxSysmanImp, NEO::Drm *pDrm, SysmanKmdInterface *pSysmanKmdInterface, uint32_t subDeviceId, bool isSubdevice) {

    std::string &rootPath = pLinuxSysmanImp->getPciRootPath();
    std::map<std::string, uint64_t> keyOffsetMap;
    std::unordered_map<std::string, std::string> keyTelemInfoMap;

    ze_result_t result = PlatformMonitoringTech::buildKeyOffsetMapFromTelemNodes(guidToKeyOffsetMap, rootPath, keyOffsetMap, keyTelemInfoMap);
    if (result != ZE_RESULT_SUCCESS) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to build key offset map from telemetry nodes, returning error:0x%x \n", __FUNCTION__, result);
        return result;
    }

    uint32_t numOfChannelsPerMsu = 0;
    std::string numOfChannelsKey = "NUM_OF_MEM_CHANNEL";
    if (!PlatformMonitoringTech::readValue(keyOffsetMap, keyTelemInfoMap[numOfChannelsKey], numOfChannelsKey, 0, numOfChannelsPerMsu)) {
        PRINT_STRING(NEO::debugManager.flags.PrintDebugMessages.get(), stderr, "Error@ %s(): Failed to read value for key: %s, returning error:0x%x \n", __FUNCTION__, numOfChannelsKey.c_str(), ZE_RESULT_ERROR_NOT_AVAILABLE);
        return ZE_RESULT_ERROR_NOT_AVAILABLE;
    }

    pProperties->location = ZES_MEM_LOC_DEVICE;
    pProperties->type = static_cast<zes_mem_type_t>(ZES_INTEL_MEM_TYPE_LPDDR5X);
    pProperties->onSubdevice = isSubdevice;
    pProperties->subdeviceId = subDeviceId;
    pProperties->numChannels = numOfChannelsPerMsu * memoryMsuCount;
    pProperties->busWidth = pProperties->numChannels * busWidthPerChannelInBytes;
    pProperties->physicalSize = 0;
    return ZE_RESULT_SUCCESS;
}

template class SysmanProductHelperHw<gfxProduct>;

} // namespace Sysman
} // namespace L0
