diff --git a/qcperf/CMakeLists.txt b/qcperf/CMakeLists.txt index 0d2627c..f6676ef 100644 --- a/qcperf/CMakeLists.txt +++ b/qcperf/CMakeLists.txt @@ -35,6 +35,30 @@ project(QcPerf include(cmake/BuildConfig.cmake) +# ============================================================================ +# Target Hardware Version +# ============================================================================ +# Specify the hardware target version to select the appropriate thermal zone +# handling strategy at compile time: +# V1 - Targetted compilation for v1 hardware +# V2 - Targeted compilation for v2 hardware +# Default - Targeted compilation for v2 hardware +set(QCPERF_TARGET_VERSION "AUTO" CACHE STRING "Target hardware version (V1, V2)") +set_property(CACHE QCPERF_TARGET_VERSION PROPERTY STRINGS V1 V2) + +if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64") + if(QCPERF_TARGET_VERSION STREQUAL "HW_API_V1") + add_compile_definitions(QCPERF_TARGET_V1) + message(STATUS "Target version: V1 (generic thermal zone support)") + elseif(QCPERF_TARGET_VERSION STREQUAL "HW_API_V2") + add_compile_definitions(QCPERF_TARGET_V2) + message(STATUS "Target HW API version: V2") + else() + add_compile_definitions(QCPERF_TARGET_V2) + message(STATUS "Default Target HW API version: V2") + endif() +endif() + # ============================================================================ # Component subdirectories # ============================================================================ @@ -61,4 +85,5 @@ message(STATUS " CXX Compiler: ${CMAKE_CXX_COMPILER}") message(STATUS " System: ${CMAKE_SYSTEM_NAME}") message(STATUS " Processor: ${CMAKE_SYSTEM_PROCESSOR}") message(STATUS " Install prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS " Target version: ${QCPERF_TARGET_VERSION}") message(STATUS "") diff --git a/qcperf/backends/utils/wos/ioctl/src/wos_ioctl.c b/qcperf/backends/utils/wos/ioctl/src/wos_ioctl.c index 7dfc854..8bbfc67 100644 --- a/qcperf/backends/utils/wos/ioctl/src/wos_ioctl.c +++ b/qcperf/backends/utils/wos/ioctl/src/wos_ioctl.c @@ -144,7 +144,7 @@ enum IoctlCommonReturnCode ioctl_get_device_handle(GUID guid, HANDLE* device_han return_code = ioctl_get_device_symbolic_link(symbolic_link_request); if (RETURN_CODE_IOCTL_COMMON_SUCCESS == return_code) { - *device_handle = CreateFile((LPCSTR)symbolic_link_request->symbolic_link, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + *device_handle = CreateFile((LPCSTR)symbolic_link_request->symbolic_link, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); error_number = GetLastError(); if (*device_handle == INVALID_HANDLE_VALUE) { return_code = RETURN_CODE_IOCTL_COMMON_CREATE_FILE_FAILED; diff --git a/qcperf/backends/utils/wos/pep/inc/qcpep_power_ioctl.h b/qcperf/backends/utils/wos/pep/inc/qcpep_power_ioctl.h new file mode 100644 index 0000000..cb7e238 --- /dev/null +++ b/qcperf/backends/utils/wos/pep/inc/qcpep_power_ioctl.h @@ -0,0 +1,85 @@ +/* + Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + Redistribution and use in source and binary forms, with or without + modification, are permitted (subject to the limitations in the + disclaimer below) 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 Qualcomm Technologies, Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE + GRANTED BY THIS LICENSE. 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 HOLDER 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. +*/ +/** + * @file qcpep_power_ioctl.h + * @brief IOCTL interface definitions for the qcpep8380 power driver (V1 hardware). + * + * Qualcomm Power Engine Plug-in Device (ACPI\VEN_QCOM&DEV_0C17, qcpep8380.sys) + * on Snapdragon 8380 (V1). The standard GUID_DEVICE_ENERGY_METER path returns + * GLE=1 on V1 firmware, so these proprietary IOCTLs are used instead. + * + * Protocol (all IOCTLs: FILE_DEVICE_UNKNOWN, METHOD_BUFFERED, FILE_READ_DATA): + * func=0x000 IOCTL_QCPEP_GET_VERSION -> uint16 driver API version (= 2) + * func=0x001 IOCTL_QCPEP_GET_CAPS_SIZE -> uint32 byte size of caps buffer + * func=0x002 IOCTL_QCPEP_GET_CAPABILITIES -> QcPepCapabilities (variable length) + * func=0x003 IOCTL_QCPEP_GET_MEASUREMENTS -> QcPepRailMeasurement[numRails] + * + * Power calculation (two consecutive GET_MEASUREMENTS calls): + * power_W = (energy2 - energy1) / (filetimeTick2 - filetimeTick1) + * (energy in 100 nJ units, FILETIME in 100 ns units, factors cancel to Watts) + */ + +#ifndef QCPEP_POWER_IOCTL_H +#define QCPEP_POWER_IOCTL_H + +#include +#include +#include + +DEFINE_GUID(GUID_QCPEP_POWER_DEVICE_INTERFACE, + 0x09195dae, 0x6bd7, 0x4a9c, 0x8f, 0x3b, 0xc8, 0x35, 0x87, 0x3d, 0x4d, 0xae); + +#define IOCTL_QCPEP_GET_VERSION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x000, METHOD_BUFFERED, FILE_READ_DATA) +#define IOCTL_QCPEP_GET_CAPS_SIZE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x001, METHOD_BUFFERED, FILE_READ_DATA) +#define IOCTL_QCPEP_GET_CAPABILITIES CTL_CODE(FILE_DEVICE_UNKNOWN, 0x002, METHOD_BUFFERED, FILE_READ_DATA) +#define IOCTL_QCPEP_GET_MEASUREMENTS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x003, METHOD_BUFFERED, FILE_READ_DATA) + +#define QCPEP_CAPS_BUFFER_MAX 512 +#define QCPEP_RAIL_INDEX_UNMATCHED 0xFF + +/* + * Fixed header of IOCTL_QCPEP_GET_CAPABILITIES response. + * Followed by numRails variable-length entries: + * { uint16_t nameBytes; WCHAR name[nameBytes/2]; } + * Use IOCTL_QCPEP_GET_CAPS_SIZE to allocate the correct buffer size. + */ +struct QcPepCapabilities { + WCHAR vendor[16]; + WCHAR model[16]; + uint16_t version; + uint16_t numRails; + uint32_t reserved; +}; + +struct QcPepRailMeasurement { + uint64_t energy100nJ; + uint64_t filetimeTick; +}; + +#endif // QCPEP_POWER_IOCTL_H diff --git a/qcperf/backends/wos-power-backend/power-telemetry/src/power_telemetry.c b/qcperf/backends/wos-power-backend/power-telemetry/src/power_telemetry.c index 9694e7e..a610e72 100644 --- a/qcperf/backends/wos-power-backend/power-telemetry/src/power_telemetry.c +++ b/qcperf/backends/wos-power-backend/power-telemetry/src/power_telemetry.c @@ -73,6 +73,16 @@ static const char* g_counter_instances[MAX_POWER_TELEMETRY_RAILS] = { }; static struct RailsSupported* g_rails_supported = NULL; +#ifdef QCPERF_TARGET_V1 +#include "qcpep_power_ioctl.h" +static HANDLE g_pep_device_handle = NULL; +static uint16_t g_v1_rail_count = 0; +static uint8_t g_v1_rail_index_map[MAX_POWER_TELEMETRY_RAILS] = {0}; +static uint64_t g_v1_prev_energy[MAX_POWER_TELEMETRY_RAILS] = {0}; +static uint64_t g_v1_prev_timestamp = 0; +static uint32_t g_v1_rails_bitmap = 0; +#endif + /** * @brief Get the supported power rails. * @@ -89,11 +99,117 @@ enum ePowerTelemetryReturnCode powerTelemetry_railsGetSupported(struct RailsSupp char* available_counters_list = NULL; uint32_t available_counters_list_length = 0; char* available_counters_list_iterator = NULL; +#ifdef QCPERF_TARGET_V1 + enum IoctlCommonReturnCode ioctl_rc = RETURN_CODE_IOCTL_COMMON_SUCCESS; + uint32_t caps_size = 0; + DWORD bytes_returned = 0; + BOOL ok = FALSE; + uint8_t* caps_buf = NULL; + const struct QcPepCapabilities* caps = NULL; + const uint8_t* entry = NULL; + const uint8_t* end = NULL; + uint16_t ri = 0; + uint16_t name_bytes = 0; + const WCHAR* wname = NULL; + char narrow[64] = {0}; + int wlen = 0; +#endif if (NULL != g_rails_supported) { SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_WARNING, "%s", "Available metrics have already been initialized, reusing"); return_code = RETURN_CODE_POWER_TELEMETRY_SUCCESS; } else { +#ifdef QCPERF_TARGET_V1 + g_rails_supported = (struct RailsSupported*)calloc(1, sizeof(struct RailsSupported)); + if (NULL == g_rails_supported) { + return_code = RETURN_CODE_POWER_TELEMETRY_CALLOC_FAILED; + } else { + ioctl_rc = ioctl_get_device_handle(GUID_QCPEP_POWER_DEVICE_INTERFACE, &g_pep_device_handle); + if (RETURN_CODE_IOCTL_COMMON_SUCCESS != ioctl_rc) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, + "ioctl_get_device_handle failed rc=%d GLE=%lu", (int)ioctl_rc, GetLastError()); + return_code = RETURN_CODE_POWER_TELEMETRY_IOCTL_HANDLE_COMMON_FAILED; + } else { + ok = DeviceIoControl(g_pep_device_handle, + IOCTL_QCPEP_GET_CAPS_SIZE, + NULL, 0, + &caps_size, (DWORD)sizeof(caps_size), + &bytes_returned, NULL); + if (FALSE == ok || bytes_returned < (DWORD)sizeof(caps_size)) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, + "IOCTL_QCPEP_GET_CAPS_SIZE failed GLE=%lu", GetLastError()); + return_code = RETURN_CODE_POWER_TELEMETRY_IOCTL_HANDLE_COMMON_FAILED; + } else if (caps_size > QCPEP_CAPS_BUFFER_MAX) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, + "caps_size=%u exceeds QCPEP_CAPS_BUFFER_MAX", caps_size); + return_code = RETURN_CODE_POWER_TELEMETRY_IOCTL_HANDLE_COMMON_FAILED; + } else { + caps_buf = (uint8_t*)calloc(caps_size, 1); + if (NULL == caps_buf) { + return_code = RETURN_CODE_POWER_TELEMETRY_CALLOC_FAILED; + } else { + ok = DeviceIoControl(g_pep_device_handle, + IOCTL_QCPEP_GET_CAPABILITIES, + NULL, 0, + caps_buf, caps_size, + &bytes_returned, NULL); + if (FALSE == ok || bytes_returned < (DWORD)sizeof(struct QcPepCapabilities)) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, + "IOCTL_QCPEP_GET_CAPABILITIES failed GLE=%lu", GetLastError()); + return_code = RETURN_CODE_POWER_TELEMETRY_IOCTL_HANDLE_COMMON_FAILED; + } else { + caps = (const struct QcPepCapabilities*)caps_buf; + g_v1_rail_count = caps->numRails; + memset(g_v1_rail_index_map, QCPEP_RAIL_INDEX_UNMATCHED, sizeof(g_v1_rail_index_map)); + entry = caps_buf + sizeof(struct QcPepCapabilities); + end = caps_buf + bytes_returned; + ri = 0; + while (ri < caps->numRails && entry + 2 <= end) { + name_bytes = *(const uint16_t*)entry; + if (name_bytes < 2u) { + entry += 2; + continue; + } + wname = (const WCHAR*)(entry + 2); + entry += 2u + name_bytes; + if (entry > end) { + break; + } + // Convert UTF-16LE rail name to narrow string + memset(narrow, 0, sizeof(narrow)); + wlen = (int)(name_bytes / 2u) - 1; // exclude null terminator + if (wlen <= 0 || wlen >= (int)sizeof(narrow)) { + ri++; + continue; + } + for (int c = 0; c < wlen; c++) { + narrow[c] = (char)(wname[c] & 0x7F); + } + for (uint8_t ei = 0; ei < MAX_POWER_TELEMETRY_RAILS; ei++) { + if (0 == strcmp(narrow, g_counter_instances[ei])) { + g_v1_rail_index_map[ri] = ei; + g_rails_supported->supportedRailsMap[ei] = true; + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_INFO, + "V1 rail[%u] '%s' -> ePowerTelemetryRail %u", + (unsigned)ri, narrow, (unsigned)ei); + return_code = RETURN_CODE_POWER_TELEMETRY_SUCCESS; + break; + } + } + if (QCPEP_RAIL_INDEX_UNMATCHED == g_v1_rail_index_map[ri]) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_WARNING, + "V1 rail[%u] '%s' has no matching ePowerTelemetryRail", + (unsigned)ri, narrow); + } + ri++; + } + } + free(caps_buf); + } + } + } + } +#else g_rails_supported = (struct RailsSupported*)calloc(1, sizeof(struct RailsSupported)); if (NULL == g_rails_supported) { SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "%s", "Memory allocation failed for available metrics"); @@ -142,6 +258,7 @@ enum ePowerTelemetryReturnCode powerTelemetry_railsGetSupported(struct RailsSupp } } } +#endif } if (RETURN_CODE_POWER_TELEMETRY_SUCCESS == return_code && g_rails_supported != rails_supported) { @@ -185,6 +302,26 @@ enum ePowerTelemetryReturnCode powerTelemetry_railsInit(struct RailsInfoRequest* SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "%s", "Input is NULL or empty"); status = RETURN_CODE_POWER_TELEMETRY_NULL_POINTER; } else { +#ifdef QCPERF_TARGET_V1 + g_rails_initialized = true; + status = powerTelemetry_railsGetSupported(NULL, true); + if (RETURN_CODE_POWER_TELEMETRY_SUCCESS == status) { + g_v1_rails_bitmap = 0; + g_v1_prev_timestamp = 0; + memset(g_v1_prev_energy, 0, sizeof(g_v1_prev_energy)); + for (uint8_t i = 0; i < request->railsLength; i++) { + uint8_t rail = request->rails[i]; + if (rail < MAX_POWER_TELEMETRY_RAILS && + g_rails_supported->supportedRailsMap[rail]) { + g_v1_rails_bitmap |= (1u << rail); + } + } + } + if (status != RETURN_CODE_POWER_TELEMETRY_SUCCESS) { + powerTelemetry_railsDestroy(); + g_rails_initialized = false; + } +#else g_rails_initialized = true; status = powerTelemetry_railsGetSupported(NULL, true); if (RETURN_CODE_POWER_TELEMETRY_SUCCESS == status) { @@ -226,6 +363,7 @@ enum ePowerTelemetryReturnCode powerTelemetry_railsInit(struct RailsInfoRequest* powerTelemetry_railsDestroy(); g_rails_initialized = false; } +#endif } return status; @@ -242,6 +380,15 @@ enum ePowerTelemetryReturnCode powerTelemetry_railsGetInfo(struct RailsInfoRespo enum ePowerTelemetryReturnCode status = RETURN_CODE_POWER_TELEMETRY_SUCCESS; uint8_t request_iterator = 0; enum ePowerTelemetryRail current_rail = POWER_TELEMETRY_RAIL_CPU_CLUSTER_0; +#ifdef QCPERF_TARGET_V1 + struct QcPepRailMeasurement meas[MAX_POWER_TELEMETRY_RAILS] = {{0}}; + DWORD bytes_returned = 0; + BOOL ok = FALSE; + uint64_t delta_time = 0; + uint8_t rail_enum = QCPEP_RAIL_INDEX_UNMATCHED; + uint64_t delta_energy = 0; + uint16_t i = 0; +#endif if (false == g_rails_initialized) { SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "%s", "Rails library is not yet initialized"); @@ -249,6 +396,62 @@ enum ePowerTelemetryReturnCode powerTelemetry_railsGetInfo(struct RailsInfoRespo } else if (NULL == response) { SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "%s", "Input is null"); status = RETURN_CODE_POWER_TELEMETRY_NULL_POINTER; +#ifdef QCPERF_TARGET_V1 + } else { + ok = DeviceIoControl(g_pep_device_handle, + IOCTL_QCPEP_GET_MEASUREMENTS, + NULL, 0, + meas, (DWORD)(sizeof(struct QcPepRailMeasurement) * g_v1_rail_count), + &bytes_returned, NULL); + if (FALSE == ok) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, + "IOCTL_QCPEP_GET_MEASUREMENTS failed GLE=%lu", GetLastError()); + status = RETURN_CODE_POWER_TELEMETRY_IOCTL_HANDLE_COMMON_FAILED; + } else if (0 == g_v1_prev_timestamp) { + for (i = 0; i < g_v1_rail_count; i++) { + g_v1_prev_energy[i] = meas[i].energy100nJ; + } + g_v1_prev_timestamp = meas[0].filetimeTick; + response->railsAndValuesLength = 0; + status = RETURN_CODE_POWER_TELEMETRY_WARNING_INVALID_DATA; + } else { + delta_time = meas[0].filetimeTick - g_v1_prev_timestamp; + if (0 == delta_time) { + response->railsAndValuesLength = 0; + status = RETURN_CODE_POWER_TELEMETRY_WARNING_INVALID_DATA; + } else { + // power_W = delta_energy_100nJ / delta_time_100ns (units cancel to Watts) + response->railsAndValuesLength = 0; + for (i = 0; i < g_v1_rail_count; i++) { + rail_enum = g_v1_rail_index_map[i]; + if (QCPEP_RAIL_INDEX_UNMATCHED == rail_enum) { + continue; + } + if (0 == (g_v1_rails_bitmap & (1u << rail_enum))) { + continue; + } + delta_energy = meas[i].energy100nJ - g_v1_prev_energy[i]; + response->rails[response->railsAndValuesLength] = rail_enum; + response->values[response->railsAndValuesLength] = (double)delta_energy / (double)delta_time; + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_DEBUG, + "V1 rail[%u] ePowerTelemetryRail=%u power=%.3fW", + (unsigned)i, (unsigned)rail_enum, + response->values[response->railsAndValuesLength]); + response->railsAndValuesLength++; + } + for (i = 0; i < g_v1_rail_count; i++) { + g_v1_prev_energy[i] = meas[i].energy100nJ; + } + g_v1_prev_timestamp = meas[0].filetimeTick; + if (response->railsAndValuesLength > 0) { + status = RETURN_CODE_POWER_TELEMETRY_SUCCESS; + } else { + status = RETURN_CODE_POWER_TELEMETRY_WARNING_INVALID_DATA; + } + } + } + } +#else } else if (NULL == g_rail_counters_requested || 0 == g_rail_counters_requested_length) { SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "%s", "Request is empty"); status = RETURN_CODE_POWER_TELEMETRY_INVALID_INPUT; @@ -286,6 +489,7 @@ enum ePowerTelemetryReturnCode powerTelemetry_railsGetInfo(struct RailsInfoRespo } } } +#endif return status; } @@ -297,6 +501,22 @@ enum ePowerTelemetryReturnCode powerTelemetry_railsGetInfo(struct RailsInfoRespo enum ePowerTelemetryReturnCode powerTelemetry_railsDestroy() { enum ePowerTelemetryReturnCode status = RETURN_CODE_POWER_TELEMETRY_SUCCESS; if (true == g_rails_initialized) { +#ifdef QCPERF_TARGET_V1 + if (NULL != g_pep_device_handle) { + ioctl_free_device_handle(&g_pep_device_handle); + g_pep_device_handle = NULL; + } + g_v1_rail_count = 0; + g_v1_rails_bitmap = 0; + g_v1_prev_timestamp = 0; + memset(g_v1_rail_index_map, QCPEP_RAIL_INDEX_UNMATCHED, sizeof(g_v1_rail_index_map)); + memset(g_v1_prev_energy, 0, sizeof(g_v1_prev_energy)); + g_rails_initialized = false; + if (NULL != g_rails_supported) { + free(g_rails_supported); + g_rails_supported = NULL; + } +#else if (NULL != g_h_query) { // Closes all counters contained in the specified query, // closes all handles related to the query, and frees all memory associated with the query. @@ -320,6 +540,7 @@ enum ePowerTelemetryReturnCode powerTelemetry_railsDestroy() { g_rails_supported = NULL; } g_rails_initialized = false; +#endif } return status; } diff --git a/qcperf/backends/wos-thermal/inc/wos_thermal_info.h b/qcperf/backends/wos-thermal/inc/wos_thermal_info.h index c729607..81ac0ef 100644 --- a/qcperf/backends/wos-thermal/inc/wos_thermal_info.h +++ b/qcperf/backends/wos-thermal/inc/wos_thermal_info.h @@ -53,7 +53,11 @@ #define WOS_THERMAL_SAMPLING_RATES 50, 100 #define WOS_THERMAL_CAPABILITY "thermal" -#define WOS_THERMAL_CAPABILITY_METRIC_COUNT 44 // 22 thermal zones * 2 metrics (temperature and passive cooling) +#if defined(QCPERF_TARGET_V2) +#define WOS_THERMAL_CAPABILITY_METRIC_COUNT 44 // V2: 22 named zones * 2 metrics (temperature and passive cooling) +#else +#define WOS_THERMAL_CAPABILITY_METRIC_COUNT 128 // V1/AUTO: up to 64 zones * 2 metrics (temperature and passive cooling) +#endif /** * @enum WosThermalMetricIndex diff --git a/qcperf/backends/wos-thermal/src/wos_thermal_info.c b/qcperf/backends/wos-thermal/src/wos_thermal_info.c index 502683f..2aceaa1 100644 --- a/qcperf/backends/wos-thermal/src/wos_thermal_info.c +++ b/qcperf/backends/wos-thermal/src/wos_thermal_info.c @@ -1185,9 +1185,44 @@ void wos_thermal_capability_init_available_metrics(struct QcPerfMetricInfo* metr found_match = true; } - // If no match was found, use generic naming + // Handle unrecognized zones based on compile-time target version flag. + // QCPERF_TARGET_V2 : named zones only — flag unrecognized zones as errors. + // QCPERF_TARGET_V1 : all zones are raw BIOS names — always create generic metrics. + // AUTO (no flag) : runtime detection — try named match first, fall back to generic. if (!found_match) { +#if defined(QCPERF_TARGET_V2) + // V2 target: every zone is expected to have a named metric definition SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Metric not defined for zone : %s", zone_name); +#else + // V1 target or AUTO: create generic metrics for unrecognized zones + if (metric_index + 1 >= WOS_THERMAL_CAPABILITY_METRIC_COUNT) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_WARNING, "Maximum metric count reached, skipping zone : %s", zone_name); + } else { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_DEBUG, "No named metric for zone : %s (zone_id=%u), creating generic metrics", zone_name, zone_id); + + // Generic temperature metric + metrics_data[metric_index].metric_id = metric_index; + snprintf(metrics_data[metric_index].metric_name, METRIC_NAME_MAX_LEN, "Zone id : %u %s Temperature", zone_id, zone_name); + metrics_data[metric_index].metric_name_len = strlen(metrics_data[metric_index].metric_name); + snprintf(metrics_data[metric_index].metric_description, MAX_METRIC_DESCRIPTION_LEN, "Temperature of thermal zone %u (%s)", zone_id, zone_name); + metrics_data[metric_index].metric_description_len = strlen(metrics_data[metric_index].metric_description); + snprintf(metrics_data[metric_index].metric_unit, MAX_METRIC_UNIT_LEN, "%s", "deg C"); + metrics_data[metric_index].metric_unit_len = strlen(metrics_data[metric_index].metric_unit); + g_zone_id_to_metric_index_map[zone_id][0] = metric_index; + metric_index++; + + // Generic passive cooling metric + metrics_data[metric_index].metric_id = metric_index; + snprintf(metrics_data[metric_index].metric_name, METRIC_NAME_MAX_LEN, "Zone id : %u %s Passive Cooling", zone_id, zone_name); + metrics_data[metric_index].metric_name_len = strlen(metrics_data[metric_index].metric_name); + snprintf(metrics_data[metric_index].metric_description, MAX_METRIC_DESCRIPTION_LEN, "Passive Cooling of thermal zone %u (%s)", zone_id, zone_name); + metrics_data[metric_index].metric_description_len = strlen(metrics_data[metric_index].metric_description); + snprintf(metrics_data[metric_index].metric_unit, MAX_METRIC_UNIT_LEN, "%s", "%"); + metrics_data[metric_index].metric_unit_len = strlen(metrics_data[metric_index].metric_unit); + g_zone_id_to_metric_index_map[zone_id][1] = metric_index; + metric_index++; + } +#endif } } }