diff --git a/README.md b/README.md index 258aa2b..9df2703 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ Originally inspired by the Qualcomm Profiler tool, libqcperf is now available as | **Power Backend** | Windows on Snapdragon (WoS) | `POWER` | `QCPERF_ENABLED_WOS_POWER` | Measures power consumption across various system components | [wos_power_backend_info.h](qcperf/backends/wos-power-backend/inc/wos_power_backend_info.h) | | **WoS CPU Backend** | Windows on Snapdragon (WoS) | `CPU` | `QCPERF_ENABLED_WOS_CPU` | Monitors total CPU utilization and average frequency, plus per-core utilization, current frequency, and max frequency | [wos_cpu_info.h](qcperf/backends/wos-cpu/inc/wos_cpu_info.h) | | **Linux CPU Backend** | Qcom Linux | `CPU` | `QCPERF_ENABLED_QCOM_LINUX_CPU` | Monitors CPU load, frequency, effective utilization, steal time, and DCVS frequency limit per core (up to 18 cores) | [qcom_linux_cpu_info.h](qcperf/backends/qcom-linux-cpu/inc/qcom_linux_cpu_info.h) | +| **Linux GPU Backend** | Qcom Linux | `GPU` | `QCPERF_ENABLED_QCOM_LINUX_GPU` | Monitors Adreno GPU utilization and clock frequency from the kgsl sysfs nodes | [qcom_linux_gpu_info.h](qcperf/backends/qcom-linux-gpu/inc/qcom_linux_gpu_info.h) | | **DSP NPU Backend** | Qcom Linux | `NPU` | `QCPERF_ENABLED_QCOM_LINUX_NPU` | Monitors Neural Processing Unit (NPU) performance metrics including Q6 utilization, Q6 clock frequency, HVX utilization, and HMX utilization | [qcom_dsp_npu_info.h](qcperf/backends/qcom-dsp-npu/inc/qcom_dsp_npu_info.h) | | **Linux Thermal Backend** | Qcom Linux | `THERMAL` | `QCPERF_ENABLED_QCOM_LINUX_THERMAL` | Monitors kernel thermal-zone temperatures and cooling-device throttle levels via `/sys/class/thermal`, aggregated into 16 temperature and 12 cooling domains (CPU, GPU, NPU, DDR, Modem, etc.); only domains with sensors present on the platform are advertised | [linux_thermal_info.h](qcperf/backends/linux-thermal/inc/linux_thermal_info.h) | | **DSP NPU Backend** | Windows on Snapdragon (WoS) | `NPU` | `QCPERF_ENABLED_WOS_NPU` | Monitors Neural Processing Unit (NPU) performance metrics including Q6 utilization, Q6 clock frequency, HVX utilization, and HMX utilization | [qcom_dsp_npu_info.h](qcperf/backends/qcom-dsp-npu/inc/qcom_dsp_npu_info.h) | @@ -240,7 +241,7 @@ QcPerfCoreTest | Argument | Description | |----------|-------------| -| `backend_id` | Numeric backend to connect to: `0` = DUMMY, `1` = CPU, `2` = NPU | +| `backend_id` | Numeric backend to connect to: `0` = DUMMY, `1` = CPU, `2` = NPU, `7` = GPU | | `sampling_rate_ms` | Sampling rate in milliseconds | | `streaming_rate_ms` | Streaming (callback) rate in milliseconds | | `verbose` | `0` = off, `1` = on | diff --git a/qcperf/backends/CMakeLists.txt b/qcperf/backends/CMakeLists.txt index 9569c15..62b61c3 100644 --- a/qcperf/backends/CMakeLists.txt +++ b/qcperf/backends/CMakeLists.txt @@ -38,6 +38,10 @@ if(QCPERF_ENABLED_QCOM_LINUX_CPU) add_subdirectory(qcom-linux-cpu) endif() +if(QCPERF_ENABLED_QCOM_LINUX_GPU) + add_subdirectory(qcom-linux-gpu) +endif() + if(QCPERF_ENABLED_QCOM_LINUX_NPU) add_subdirectory(qcom-dsp) add_subdirectory(qcom-dsp-npu) diff --git a/qcperf/backends/inc/CMakeLists.txt b/qcperf/backends/inc/CMakeLists.txt index 006ea51..eceaccb 100644 --- a/qcperf/backends/inc/CMakeLists.txt +++ b/qcperf/backends/inc/CMakeLists.txt @@ -53,6 +53,15 @@ if(QCPERF_ENABLED_QCOM_LINUX_CPU) ) endif() +if(QCPERF_ENABLED_QCOM_LINUX_GPU) + target_include_directories(QcPerfBackends INTERFACE + ${CMAKE_SOURCE_DIR}/backends/qcom-linux-gpu/inc + ) + target_link_libraries(QcPerfBackends INTERFACE + QcPerfQcomLinuxGpuBackend + ) +endif() + if(QCPERF_ENABLED_QCOM_LINUX_NPU OR QCPERF_ENABLED_WOS_NPU) target_include_directories(QcPerfBackends INTERFACE ${CMAKE_SOURCE_DIR}/backends/qcom-dsp-npu/inc diff --git a/qcperf/backends/inc/qcperf_backend_enum.h b/qcperf/backends/inc/qcperf_backend_enum.h index 354d0d8..33699c9 100644 --- a/qcperf/backends/inc/qcperf_backend_enum.h +++ b/qcperf/backends/inc/qcperf_backend_enum.h @@ -70,6 +70,7 @@ enum QcPerfBackendId { QC_PERF_BACKEND_THERMAL, /**< Thermal monitoring backend (Windows ARM64) */ QC_PERF_BACKEND_WOS_CPU, /**< CPU monitoring backend (Windows ARM64) */ QC_PERF_BACKEND_LINUX_THERMAL, /**< Thermal monitoring backend (Linux, /sys/class/thermal) */ + QC_PERF_BACKEND_QCOM_LINUX_GPU, /**< Qualcomm Linux GPU monitoring backend (Linux ARM64) */ QC_PERF_BACKEND_MAX, /**< Sentinel value — total number of backend slots */ }; diff --git a/qcperf/backends/inc/qcperf_backends.h b/qcperf/backends/inc/qcperf_backends.h index 7824858..8858cd4 100644 --- a/qcperf/backends/inc/qcperf_backends.h +++ b/qcperf/backends/inc/qcperf_backends.h @@ -63,6 +63,10 @@ #include "qcom_dsp_npu.h" #endif +#if defined(QCPERF_ENABLED_QCOM_LINUX_GPU) +#include "qcom_linux_gpu.h" +#endif + #if defined(QCPERF_ENABLED_WOS_POWER) #include "wos_power_backend.h" #endif @@ -135,6 +139,11 @@ static backend_init_t backend_init_fns[] = { #else NULL, /* QC_PERF_BACKEND_LINUX_THERMAL (disabled) */ #endif +#if defined(QCPERF_ENABLED_QCOM_LINUX_GPU) + &qcperf_qcom_linux_gpu_create, /* QC_PERF_BACKEND_QCOM_LINUX_GPU */ +#else + NULL, /* QC_PERF_BACKEND_QCOM_LINUX_GPU (disabled) */ +#endif }; #endif // QC_PERF_BACKENDS_H diff --git a/qcperf/backends/qcom-linux-gpu/CMakeLists.txt b/qcperf/backends/qcom-linux-gpu/CMakeLists.txt new file mode 100644 index 0000000..a17b885 --- /dev/null +++ b/qcperf/backends/qcom-linux-gpu/CMakeLists.txt @@ -0,0 +1,27 @@ +# ============================================================================ +# QcPerf Qualcomm Linux GPU Backend +# ============================================================================ +# Reports Adreno GPU utilization and clock from the kgsl sysfs nodes. + +message("Added qcom-linux-gpu backend") + +add_library(QcPerfQcomLinuxGpuBackend STATIC + src/qcom_linux_gpu.c + src/qcom_linux_gpu_info.c + src/qcom_linux_gpu_utils.c +) + +target_include_directories(QcPerfQcomLinuxGpuBackend PUBLIC + inc + ${CMAKE_SOURCE_DIR}/core/inc + ${CMAKE_SOURCE_DIR}/core/inc/internal + ${CMAKE_SOURCE_DIR}/backends/inc + ${CMAKE_SOURCE_DIR}/utils/qthread/inc + ${CMAKE_SOURCE_DIR}/utils/qtime/inc +) + +target_link_libraries(QcPerfQcomLinuxGpuBackend + QcPerfQThread + QcPerfQTime + ${_OS_M} +) diff --git a/qcperf/backends/qcom-linux-gpu/inc/qcom_linux_gpu.h b/qcperf/backends/qcom-linux-gpu/inc/qcom_linux_gpu.h new file mode 100644 index 0000000..55f16af --- /dev/null +++ b/qcperf/backends/qcom-linux-gpu/inc/qcom_linux_gpu.h @@ -0,0 +1,54 @@ +/* + 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 qcom_linux_gpu.h + * @brief Qualcomm Linux GPU backend interface for libqcperf + * + * Reports Adreno GPU utilization and clock read from the kgsl sysfs nodes. + */ + +#ifndef QCOM_LINUX_GPU_H +#define QCOM_LINUX_GPU_H + +#include "qcom_linux_gpu_info.h" + +struct QcPerfBackendPrivate; + +/** + * @brief Create and configure the Linux GPU backend + * + * @param[in,out] backend Backend private structure to fill with the backend's function pointers + * + * @return QC_PERF_RETURN_CODE_SUCCESS on success + * @return QC_PERF_RETURN_CODE_NULL_POINTER if backend is NULL + */ +enum QcPerfReturnCode qcperf_qcom_linux_gpu_create(struct QcPerfBackendPrivate* backend); + +#endif /* QCOM_LINUX_GPU_H */ diff --git a/qcperf/backends/qcom-linux-gpu/inc/qcom_linux_gpu_info.h b/qcperf/backends/qcom-linux-gpu/inc/qcom_linux_gpu_info.h new file mode 100644 index 0000000..9f05158 --- /dev/null +++ b/qcperf/backends/qcom-linux-gpu/inc/qcom_linux_gpu_info.h @@ -0,0 +1,65 @@ +/* + 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 qcom_linux_gpu_info.h + * @brief Static metric definitions for the qcom-linux-gpu backend + */ + +#ifndef QCOM_LINUX_GPU_INFO_H +#define QCOM_LINUX_GPU_INFO_H + +#include "qcperf_common.h" + +#define LINUX_GPU_CAPABILITY_0_ID 0 +#define LINUX_GPU_CAPABILITIES_LEN 1 + +#define LINUX_GPU_STREAMING_RATES_LEN 10 +#define LINUX_GPU_STREAMING_RATES 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 + +#define LINUX_GPU_SAMPLING_RATES_LEN 6 +#define LINUX_GPU_SAMPLING_RATES 1, 5, 10, 50, 100, 200 + +#define LINUX_GPU_CAPABILITY_0 "gpu" +#define LINUX_GPU_CAPABILITY_0_METRIC_COUNT 2 + +/** Metric IDs; each is also the metric's index in the capability's metric array. */ +enum LinuxGpuMetricId { + LINUX_GPU_METRIC_UTILIZATION = 0, /**< GPU utilization, % */ + LINUX_GPU_METRIC_CLOCK, /**< Current GPU clock, MHz */ +}; + +/** + * @brief Populate the metric descriptions for capability 0 + * + * @param[out] metrics_data Array with space for LINUX_GPU_CAPABILITY_0_METRIC_COUNT entries + */ +void linux_gpu_capability_0_init_metrics(struct QcPerfMetricInfo* metrics_data); + +#endif /* QCOM_LINUX_GPU_INFO_H */ diff --git a/qcperf/backends/qcom-linux-gpu/inc/qcom_linux_gpu_utils.h b/qcperf/backends/qcom-linux-gpu/inc/qcom_linux_gpu_utils.h new file mode 100644 index 0000000..11a4144 --- /dev/null +++ b/qcperf/backends/qcom-linux-gpu/inc/qcom_linux_gpu_utils.h @@ -0,0 +1,71 @@ +/* + 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 qcom_linux_gpu_utils.h + * @brief Sysfs readers for the qcom-linux-gpu backend + * + * The Adreno kgsl driver exposes GPU state under /sys/class/kgsl/kgsl-3d0. + * Every reader takes the sysfs directory as a parameter instead of + * hard-coding it. + */ + +#ifndef QCOM_LINUX_GPU_UTILS_H +#define QCOM_LINUX_GPU_UTILS_H + +#include "qcperf_common.h" + +/** Default kgsl sysfs directory for the GPU */ +#define QCOM_LINUX_GPU_SYSFS_ROOT "/sys/class/kgsl/kgsl-3d0" + +/** File holding GPU busy time as " %" */ +#define QCOM_LINUX_GPU_BUSY_NODE "gpu_busy_percentage" + +/** File holding the current GPU clock in Hz */ +#define QCOM_LINUX_GPU_CLOCK_NODE "gpuclk" + +/** + * @brief Read GPU utilization + * @param[in] sysfs_root kgsl sysfs directory + * @param[out] percent utilization in percent (0-100) + * @return QC_PERF_RETURN_CODE_SUCCESS, or QC_PERF_RETURN_CODE_FAILED if the + * node is missing or its content is not a number + */ +enum QcPerfReturnCode qcom_linux_gpu_util_read_utilization(const char* sysfs_root, double* percent); + +/** + * @brief Read the current GPU clock + * @param[in] sysfs_root kgsl sysfs directory + * @param[out] mhz clock in MHz + * @return QC_PERF_RETURN_CODE_SUCCESS, or QC_PERF_RETURN_CODE_FAILED if the + * node is missing or its content is not a number + */ +enum QcPerfReturnCode qcom_linux_gpu_util_read_clock_mhz(const char* sysfs_root, double* mhz); + +#endif /* QCOM_LINUX_GPU_UTILS_H */ diff --git a/qcperf/backends/qcom-linux-gpu/src/qcom_linux_gpu.c b/qcperf/backends/qcom-linux-gpu/src/qcom_linux_gpu.c new file mode 100644 index 0000000..84a16f8 --- /dev/null +++ b/qcperf/backends/qcom-linux-gpu/src/qcom_linux_gpu.c @@ -0,0 +1,369 @@ +/* + 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 qcom_linux_gpu.c + * @brief Qualcomm Linux GPU backend implementation for libqcperf + * + * Samples Adreno GPU utilization and clock from the kgsl sysfs nodes on a + * background thread and streams them through the data callback. + */ + +#include +#include +#include +#include +#include + +#include "qcom_linux_gpu.h" +#include "qcom_linux_gpu_utils.h" +#include "qcperf_backend_enum.h" +#include "qcperf_backend_interface.h" +#include "qthread.h" +#include "qtime.h" + +#define MESSAGE_MAX_LEN 256 + +static QcPerfMessageCallback g_message_callback = NULL; +static QcPerfDataCallback g_data_callback = NULL; + +static struct QcPerfBackendInfo g_backend_info = {0}; +static struct QcPerfMetricInfo g_metrics_data[LINUX_GPU_CAPABILITY_0_METRIC_COUNT]; + +static volatile bool g_is_thread_running = false; +static struct QThreadInfo g_thread_info = {0}; + +static const uint16_t streaming_rates[LINUX_GPU_STREAMING_RATES_LEN] = {LINUX_GPU_STREAMING_RATES}; +static const uint16_t sampling_rates[LINUX_GPU_SAMPLING_RATES_LEN] = {LINUX_GPU_SAMPLING_RATES}; + +static inline void send_message(enum QcPerfMessageLevel level, const char* fmt, ...) { + char msg[MESSAGE_MAX_LEN]; + struct QcPerfMessage msg_struct = {0}; + va_list args; + int len = 0; + + va_start(args, fmt); + len = vsnprintf(msg, sizeof(msg), fmt, args); + va_end(args); + + if (len > 0) { + if (len >= MESSAGE_MAX_LEN) { + len = MESSAGE_MAX_LEN - 1; + } + msg_struct.message = msg; + msg_struct.message_length = (size_t)len; + msg_struct.message_level = level; + g_message_callback(&msg_struct); + } +} + +#define SEND_MESSAGE(level, ...) \ + if (g_message_callback != NULL) { \ + send_message(level, __VA_ARGS__); \ + } + +static bool rate_in_list(const uint16_t* list, uint8_t len, uint16_t rate) { + bool found = false; + + if (NULL != list) { + for (uint8_t i = 0; i < len && !found; i++) { + found = (list[i] == rate); + } + } + + return found; +} + +static enum QcPerfReturnCode validate_request(const struct QcPerfRequest* request) { + enum QcPerfReturnCode ret = QC_PERF_RETURN_CODE_SUCCESS; + + if (NULL == request) { + ret = QC_PERF_RETURN_CODE_NULL_POINTER; + } else if (LINUX_GPU_CAPABILITY_0_ID != request->capability_id) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Capability ID %u not found", request->capability_id); + ret = QC_PERF_RETURN_CODE_CAPABILITY_NOT_FOUND; + } else if (!rate_in_list(sampling_rates, LINUX_GPU_SAMPLING_RATES_LEN, request->sampling_rate)) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Invalid sampling rate %u", request->sampling_rate); + ret = QC_PERF_RETURN_CODE_INVALID_ARGUMENTS; + } else if (!rate_in_list(streaming_rates, LINUX_GPU_STREAMING_RATES_LEN, request->streaming_rate)) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Invalid streaming rate %u", request->streaming_rate); + ret = QC_PERF_RETURN_CODE_INVALID_ARGUMENTS; + } + + return ret; +} + +static enum QcPerfReturnCode qcom_linux_gpu_set_message_callback(QcPerfMessageCallback message_callback) { + g_message_callback = message_callback; + return QC_PERF_RETURN_CODE_SUCCESS; +} + +static enum QcPerfReturnCode qcom_linux_gpu_set_data_callback(QcPerfDataCallback data_callback) { + enum QcPerfReturnCode ret = QC_PERF_RETURN_CODE_SUCCESS; + + if (NULL == data_callback) { + ret = QC_PERF_RETURN_CODE_INVALID_ARGUMENTS; + } else if (NULL != g_data_callback) { + ret = QC_PERF_RETURN_CODE_CALLBACK_ALREADY_SET; + } else { + g_data_callback = data_callback; + } + + return ret; +} + +static enum QcPerfReturnCode qcom_linux_gpu_init(void) { + enum QcPerfReturnCode ret = QC_PERF_RETURN_CODE_SUCCESS; + double probe = 0.0; + struct QcPerfCapabilityInfo* cap = NULL; + + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_INFO, "Initializing qcom-linux-gpu backend"); + + /* Fail at connect time, not mid-profile, when the board has no kgsl GPU. */ + if (QC_PERF_RETURN_CODE_SUCCESS != qcom_linux_gpu_util_read_utilization(QCOM_LINUX_GPU_SYSFS_ROOT, &probe)) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Cannot read %s/%s", QCOM_LINUX_GPU_SYSFS_ROOT, QCOM_LINUX_GPU_BUSY_NODE); + ret = QC_PERF_RETURN_CODE_FAILED; + } else { + g_backend_info.capabilities_list = (struct QcPerfCapabilityInfo*)calloc(LINUX_GPU_CAPABILITIES_LEN, sizeof(struct QcPerfCapabilityInfo)); + if (NULL == g_backend_info.capabilities_list) { + ret = QC_PERF_RETURN_CODE_CALLOC_FAILED; + } else { + g_backend_info.backend_id = QC_PERF_BACKEND_QCOM_LINUX_GPU; + g_backend_info.capabilities_list_length = LINUX_GPU_CAPABILITIES_LEN; + + linux_gpu_capability_0_init_metrics(g_metrics_data); + + cap = &g_backend_info.capabilities_list[0]; + cap->capability_id = LINUX_GPU_CAPABILITY_0_ID; + snprintf(cap->capability_name, CAPABILITY_NAME_MAX_LEN, "%s", LINUX_GPU_CAPABILITY_0); + cap->capability_name_len = strlen(cap->capability_name); + cap->metric_ids_list = g_metrics_data; + cap->metric_ids_list_len = LINUX_GPU_CAPABILITY_0_METRIC_COUNT; + + for (uint8_t i = 0; i < LINUX_GPU_STREAMING_RATES_LEN && i < MAX_SAMPLING_STREAMING_RATES_LEN; i++) { + cap->streaming_rate[cap->streaming_rate_len++] = streaming_rates[i]; + } + for (uint8_t i = 0; i < LINUX_GPU_SAMPLING_RATES_LEN && i < MAX_SAMPLING_STREAMING_RATES_LEN; i++) { + cap->sampling_rate[cap->sampling_rate_len++] = sampling_rates[i]; + } + + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_INFO, "qcom-linux-gpu backend initialized successfully"); + } + } + + return ret; +} + +static enum QcPerfReturnCode qcom_linux_gpu_backend_info(struct QcPerfBackendInfo* backend_info) { + enum QcPerfReturnCode ret = QC_PERF_RETURN_CODE_SUCCESS; + + if (NULL == backend_info) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Backend info is NULL"); + ret = QC_PERF_RETURN_CODE_NULL_POINTER; + } else { + *backend_info = g_backend_info; + } + + return ret; +} + +/** + * @brief Sampling thread: reads sysfs every sampling_rate ms and flushes one + * batch to the data callback every streaming_rate ms. + */ +static void* collect_gpu_data(void* param) { + const struct QcPerfRequest* request = (const struct QcPerfRequest*)param; + uint64_t streaming_rate_ns = 0; + uint32_t samples_per_stream = 0; + uint32_t capacity = 0; + struct QcPerfData* data = NULL; + uint64_t now = 0; + uint64_t last_stream_time = 0; + uint32_t sample_count = 0; + + if (NULL != request) { + streaming_rate_ns = (uint64_t)request->streaming_rate * 1000000ULL; + samples_per_stream = ((uint32_t)request->streaming_rate + request->sampling_rate - 1U) / request->sampling_rate; + capacity = samples_per_stream * LINUX_GPU_CAPABILITY_0_METRIC_COUNT; + data = (struct QcPerfData*)calloc(1, sizeof(struct QcPerfData)); + } + + if (NULL != data) { + data->metric_response = (struct QcPerfMetricResponse*)calloc(capacity, sizeof(struct QcPerfMetricResponse)); + } + + if (NULL == request) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Invalid thread parameter"); + g_is_thread_running = false; + } else if (NULL == data || NULL == data->metric_response) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Failed to allocate memory for performance data"); + g_is_thread_running = false; + } else { + data->capabilityId = request->capability_id; + data->backend_id = QC_PERF_BACKEND_QCOM_LINUX_GPU; + + get_time_ns(&now); + last_stream_time = now; + + while (g_is_thread_running) { + double values[LINUX_GPU_CAPABILITY_0_METRIC_COUNT] = {0}; + bool valid[LINUX_GPU_CAPABILITY_0_METRIC_COUNT] = {false}; + bool any_valid = false; + + get_time_ns(&now); + + /* A missing node only drops its own metric; the others are still reported. */ + valid[LINUX_GPU_METRIC_UTILIZATION] = (QC_PERF_RETURN_CODE_SUCCESS == qcom_linux_gpu_util_read_utilization(QCOM_LINUX_GPU_SYSFS_ROOT, &values[LINUX_GPU_METRIC_UTILIZATION])); + valid[LINUX_GPU_METRIC_CLOCK] = (QC_PERF_RETURN_CODE_SUCCESS == qcom_linux_gpu_util_read_clock_mhz(QCOM_LINUX_GPU_SYSFS_ROOT, &values[LINUX_GPU_METRIC_CLOCK])); + + for (uint32_t m = 0; m < LINUX_GPU_CAPABILITY_0_METRIC_COUNT; m++) { + any_valid = any_valid || valid[m]; + } + + if (any_valid && data->metric_response_len + LINUX_GPU_CAPABILITY_0_METRIC_COUNT <= capacity) { + for (uint32_t m = 0; m < LINUX_GPU_CAPABILITY_0_METRIC_COUNT; m++) { + if (valid[m]) { + struct QcPerfMetricResponse* r = &data->metric_response[data->metric_response_len++]; + + r->metric_id = g_metrics_data[m].metric_id; + r->timestamp = now; + r->metric_value.data_type = QC_PERF_DATA_TYPE_DOUBLE; + r->metric_value.double_value = values[m]; + } + } + } else { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_WARNING, "Failed to read GPU sysfs nodes"); + } + + sample_count++; + if ((sample_count >= samples_per_stream || now - last_stream_time >= streaming_rate_ns) && data->metric_response_len > 0) { + g_data_callback(data); + data->metric_response_len = 0; + sample_count = 0; + last_stream_time = now; + } + + if (g_is_thread_running) { + usleep((useconds_t)(request->sampling_rate * 1000)); + } + } + } + + if (NULL != data) { + free(data->metric_response); + free(data); + } + + return NULL; +} + +static enum QcPerfReturnCode qcom_linux_gpu_start(struct QcPerfRequest* request) { + enum QcPerfReturnCode ret = QC_PERF_RETURN_CODE_SUCCESS; + struct QThreadAttributes thread_attrs = {0}; + + if (NULL == request) { + ret = QC_PERF_RETURN_CODE_NULL_POINTER; + } else if (QC_PERF_RETURN_CODE_SUCCESS != (ret = validate_request(request))) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Request validation failed"); + } else if (NULL == g_data_callback) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Data callback is not set"); + ret = QC_PERF_RETURN_CODE_FAILED; + } else if (NULL != g_thread_info.thread_handle) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_WARNING, "Performance monitoring thread is already running"); + ret = QC_PERF_RETURN_CODE_ALREADY_INITIALIZED; + } else { + thread_attrs.stack_size = 0; + thread_attrs.thread_params = request; + thread_attrs.thread_fn = collect_gpu_data; + snprintf((char*)thread_attrs.thread_name, THREAD_NAME_SIZE, "qcperf_gpu_thread"); + thread_attrs.thread_name_len = (uint8_t)strlen((char*)thread_attrs.thread_name); + + /* Set before the thread exists so a stop() right after start() still joins it. */ + g_is_thread_running = true; + if (RET_QTHREAD_CREATE_SUCCESS != thread_create(&thread_attrs, &g_thread_info)) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Failed to create performance monitoring thread"); + g_is_thread_running = false; + ret = QC_PERF_RETURN_CODE_FAILED; + } + } + + return ret; +} + +static enum QcPerfReturnCode qcom_linux_gpu_stop(struct QcPerfRequest* request) { + enum QcPerfReturnCode ret = QC_PERF_RETURN_CODE_SUCCESS; + + if (NULL == request) { + ret = QC_PERF_RETURN_CODE_NULL_POINTER; + } else if (NULL != g_thread_info.thread_handle) { + /* The sampling thread may have exited on its own (allocation failure), so + * ownership follows the handle, not g_is_thread_running. */ + g_is_thread_running = false; + + if (RET_QTHREAD_JOIN_SUCCESS != thread_join(&g_thread_info)) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Failed to join performance monitoring thread"); + ret = QC_PERF_RETURN_CODE_FAILED; + } + + /* Always release the handle, even when the join failed. */ + if (RET_QTHREAD_DESTROY_SUCCESS != thread_destroy(&g_thread_info)) { + SEND_MESSAGE(QC_PERF_MESSAGE_LEVEL_ERROR, "Failed to destroy performance monitoring thread"); + ret = QC_PERF_RETURN_CODE_FAILED; + } + } + + return ret; +} + +static enum QcPerfReturnCode qcom_linux_gpu_deinit(void) { + free(g_backend_info.capabilities_list); + g_backend_info.capabilities_list = NULL; + g_backend_info.capabilities_list_length = 0; + g_data_callback = NULL; + + return QC_PERF_RETURN_CODE_SUCCESS; +} + +enum QcPerfReturnCode qcperf_qcom_linux_gpu_create(struct QcPerfBackendPrivate* backend) { + enum QcPerfReturnCode ret = QC_PERF_RETURN_CODE_SUCCESS; + + if (NULL == backend) { + ret = QC_PERF_RETURN_CODE_NULL_POINTER; + } else { + backend->qcperf_backend_init = qcom_linux_gpu_init; + backend->qcperf_backend_start = qcom_linux_gpu_start; + backend->qcperf_backend_stop = qcom_linux_gpu_stop; + backend->qcperf_backend_deinit = qcom_linux_gpu_deinit; + backend->qcperf_backend_info = qcom_linux_gpu_backend_info; + backend->set_message_callback = qcom_linux_gpu_set_message_callback; + backend->set_data_callback = qcom_linux_gpu_set_data_callback; + } + + return ret; +} diff --git a/qcperf/backends/qcom-linux-gpu/src/qcom_linux_gpu_info.c b/qcperf/backends/qcom-linux-gpu/src/qcom_linux_gpu_info.c new file mode 100644 index 0000000..6afeceb --- /dev/null +++ b/qcperf/backends/qcom-linux-gpu/src/qcom_linux_gpu_info.c @@ -0,0 +1,65 @@ +/* + 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 qcom_linux_gpu_info.c + * @brief Metric description tables for the qcom-linux-gpu backend + */ + +#include +#include + +#include "qcom_linux_gpu_info.h" + +struct MetricDefinition { + const char* name; + const char* description; + const char* unit; +}; + +static const struct MetricDefinition k_metrics[LINUX_GPU_CAPABILITY_0_METRIC_COUNT] = { + [LINUX_GPU_METRIC_UTILIZATION] = {"GPU Utilization", "Percentage of time the GPU was busy", "%"}, + [LINUX_GPU_METRIC_CLOCK] = {"GPU Clock", "Current GPU clock frequency", "MHz"}, +}; + +void linux_gpu_capability_0_init_metrics(struct QcPerfMetricInfo* metrics_data) { + struct QcPerfMetricInfo* m = NULL; + + for (uint32_t i = 0; i < LINUX_GPU_CAPABILITY_0_METRIC_COUNT; i++) { + m = &metrics_data[i]; + + m->metric_id = (uint16_t)i; + snprintf(m->metric_name, METRIC_NAME_MAX_LEN, "%s", k_metrics[i].name); + m->metric_name_len = strlen(m->metric_name); + snprintf(m->metric_description, MAX_METRIC_DESCRIPTION_LEN, "%s", k_metrics[i].description); + m->metric_description_len = strlen(m->metric_description); + snprintf(m->metric_unit, MAX_METRIC_UNIT_LEN, "%s", k_metrics[i].unit); + m->metric_unit_len = strlen(m->metric_unit); + } +} diff --git a/qcperf/backends/qcom-linux-gpu/src/qcom_linux_gpu_utils.c b/qcperf/backends/qcom-linux-gpu/src/qcom_linux_gpu_utils.c new file mode 100644 index 0000000..aade83a --- /dev/null +++ b/qcperf/backends/qcom-linux-gpu/src/qcom_linux_gpu_utils.c @@ -0,0 +1,74 @@ +/* + 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 qcom_linux_gpu_utils.c + * @brief Sysfs reader implementations for the qcom-linux-gpu backend + */ + +#include + +#include "qcom_linux_gpu_utils.h" + +static enum QcPerfReturnCode read_sysfs_value(const char* sysfs_root, const char* node, double* value) { + enum QcPerfReturnCode ret = QC_PERF_RETURN_CODE_FAILED; + char path[256]; + FILE* fp = NULL; + + if (NULL == sysfs_root || NULL == value || NULL == node) { + ret = QC_PERF_RETURN_CODE_NULL_POINTER; + } else if (snprintf(path, sizeof(path), "%s/%s", sysfs_root, node) < (int)sizeof(path)) { + fp = fopen(path, "r"); + if (NULL != fp) { + /* Trailing text such as the " %" in gpu_busy_percentage is ignored. */ + if (1 == fscanf(fp, "%lf", value)) { + ret = QC_PERF_RETURN_CODE_SUCCESS; + } + fclose(fp); + } + } + + return ret; +} + +enum QcPerfReturnCode qcom_linux_gpu_util_read_utilization(const char* sysfs_root, double* percent) { return read_sysfs_value(sysfs_root, QCOM_LINUX_GPU_BUSY_NODE, percent); } + +enum QcPerfReturnCode qcom_linux_gpu_util_read_clock_mhz(const char* sysfs_root, double* mhz) { + double hz = 0.0; + enum QcPerfReturnCode ret = QC_PERF_RETURN_CODE_NULL_POINTER; + + if (NULL != mhz) { + ret = read_sysfs_value(sysfs_root, QCOM_LINUX_GPU_CLOCK_NODE, &hz); + if (QC_PERF_RETURN_CODE_SUCCESS == ret) { + *mhz = hz / 1000000.0; + } + } + + return ret; +} diff --git a/qcperf/cmake/BuildConfig.cmake b/qcperf/cmake/BuildConfig.cmake index e4dd6e5..b9be8d8 100644 --- a/qcperf/cmake/BuildConfig.cmake +++ b/qcperf/cmake/BuildConfig.cmake @@ -1,402 +1,402 @@ -# ============================================================================ -# QcPerf - Combined Build Configuration -# ============================================================================ -# This file centralizes all build configuration for the QcPerf library: -# - Platform detection (OS and architecture) -# - Platform-specific compiler flags and definitions -# - Compiler configuration and optimization settings -# - Platform types and standard header checks -# -# It is designed to support multiple platforms (Windows, Linux) and -# architectures (x86_64, ARM64) with a focus on Windows ARM64 support. -# ============================================================================ - -# ============================================================================ -# Platform Detection -# ============================================================================ -# Detect operating system and architecture to set appropriate build flags -# and definitions. This enables conditional compilation for platform-specific -# code throughout the project. - -# Detect OS -if(WIN32) - set(QCPERF_OS_WINDOWS TRUE) - message(STATUS "Detected Windows platform") -elseif(ANDROID) - set(QCPERF_OS_LINUX TRUE) - set(QCPERF_OS_ANDROID TRUE) - message(STATUS "Detected Android platform") -elseif(UNIX) - set(QCPERF_OS_LINUX TRUE) - message(STATUS "Detected Linux platform") -else() - message(WARNING "Unknown platform detected") -endif() - -# Detect architecture -# For Visual Studio, we need to check CMAKE_VS_PLATFORM_NAME -if(MSVC AND CMAKE_VS_PLATFORM_NAME MATCHES "ARM64") - set(QCPERF_ARCH_ARM64 TRUE) - # Override CMAKE_SYSTEM_PROCESSOR for Visual Studio ARM64 builds - set(CMAKE_SYSTEM_PROCESSOR "ARM64") - message(STATUS "Detected ARM64 architecture (Visual Studio)") -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64") - set(QCPERF_ARCH_X86_64 TRUE) - message(STATUS "Detected x86_64 architecture") -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|arm64") - set(QCPERF_ARCH_ARM64 TRUE) - message(STATUS "Detected ARM64 architecture") -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i686|i386") - set(QCPERF_ARCH_X86 TRUE) - message(STATUS "Detected x86 architecture") -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "armv7|armhf") - set(QCPERF_ARCH_ARM32 TRUE) - message(STATUS "Detected ARM32 architecture") -else() - message(STATUS "Architecture detection using CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") - if(DEFINED CMAKE_VS_PLATFORM_NAME) - message(STATUS "Visual Studio platform name: ${CMAKE_VS_PLATFORM_NAME}") - endif() - message(WARNING "Unknown architecture - defaulting to x86_64") - set(QCPERF_ARCH_X86_64 TRUE) -endif() - -# Set combined platform variables for convenience -if(QCPERF_OS_WINDOWS AND QCPERF_ARCH_ARM64) - set(QCPERF_PLATFORM_WINDOWS_ARM64 TRUE) -elseif(QCPERF_OS_WINDOWS AND QCPERF_ARCH_X86_64) - set(QCPERF_PLATFORM_WINDOWS_X86_64 TRUE) -elseif(QCPERF_OS_LINUX AND QCPERF_ARCH_ARM64) - set(QCPERF_PLATFORM_LINUX_ARM64 TRUE) -elseif(QCPERF_OS_LINUX AND QCPERF_ARCH_X86_64) - set(QCPERF_PLATFORM_LINUX_X86_64 TRUE) -endif() - -# Print platform summary -message(STATUS "QcPerf Platform Configuration:") -message(STATUS " OS: ${CMAKE_SYSTEM_NAME}") -message(STATUS " Architecture: ${CMAKE_SYSTEM_PROCESSOR}") -if(CMAKE_CROSSCOMPILING) - message(STATUS " Cross-compiling: YES (host=${CMAKE_HOST_SYSTEM_PROCESSOR} -> target=${CMAKE_SYSTEM_PROCESSOR})") - message(STATUS " C compiler: ${CMAKE_C_COMPILER}") -else() - message(STATUS " Cross-compiling: NO (native build)") -endif() - -# ============================================================================ -# Platform-Specific Configurations -# ============================================================================ -# Apply platform-specific compiler flags, definitions, and optimizations -# based on the detected OS and architecture combinations. - -# Windows-specific configurations -if(QCPERF_OS_WINDOWS) - # Enable Windows-specific definitions - add_compile_definitions(QCPERF_PLATFORM_WINDOWS) - - # Windows-specific compiler flags - if(MSVC) - # Enable multi-processor compilation - add_compile_options(/MP) - - # Warning level - add_compile_options(/W4) - - # Disable specific warnings if needed - # add_compile_options(/wd4100) # Unused parameter - - # Enable additional security features - add_compile_definitions( - _CRT_SECURE_NO_WARNINGS - _SCL_SECURE_NO_WARNINGS - ) - endif() - - # ARM64 specific configurations - if(QCPERF_PLATFORM_WINDOWS_ARM64) - message(STATUS "Configuring for Windows ARM64") - - # Add ARM64-specific compile definitions - add_compile_definitions(WIN32_ARM64) - endif() - - # x86_64 specific configurations - if(QCPERF_PLATFORM_WINDOWS_X86_64) - message(STATUS "Configuring for Windows x86_64") - endif() - - message(STATUS "Windows platform configuration loaded") -endif() - -# ============================================================================ -# Backend Selection -# ============================================================================ -# Determine which backends are supported on the current platform. -# For each OS-specific backend, QCPERF_BACKEND_OS_PREFIX_ is set to the -# OS qualifier (e.g. QCOM_LINUX, WOS) so the compile definition becomes -# QCPERF_ENABLED__. DUMMY has no OS prefix and remains QCPERF_ENABLED_DUMMY. -set(QCPERF_PLATFORM_SUPPORTED_BACKENDS "DUMMY") - -if(QCPERF_PLATFORM_LINUX_ARM64) - set(_OS_PREFIX "QCOM_LINUX") - foreach(_BACKEND CPU NPU THERMAL) - list(APPEND QCPERF_PLATFORM_SUPPORTED_BACKENDS ${_BACKEND}) - set(QCPERF_BACKEND_OS_PREFIX_${_BACKEND} ${_OS_PREFIX}) - endforeach() -endif() - -if(QCPERF_PLATFORM_WINDOWS_ARM64) - set(_OS_PREFIX "WOS") - foreach(_BACKEND THERMAL POWER CPU NPU) - list(APPEND QCPERF_PLATFORM_SUPPORTED_BACKENDS ${_BACKEND}) - set(QCPERF_BACKEND_OS_PREFIX_${_BACKEND} ${_OS_PREFIX}) - endforeach() -endif() - -# If empty (default), all platform-supported backends are enabled. -set(BACKENDS "" CACHE STRING - "Semicolon-separated list of backends to enable (e.g. \"DUMMY;CPU;NPU\"). \ -If empty, all platform-supported backends are enabled.") - -if(BACKENDS) - # User specified an explicit list — enable only those supported on this platform - foreach(_BACKEND_INPUT ${BACKENDS}) - string(TOUPPER "${_BACKEND_INPUT}" _BACKEND) - if(${_BACKEND} IN_LIST QCPERF_PLATFORM_SUPPORTED_BACKENDS) - if(DEFINED QCPERF_BACKEND_OS_PREFIX_${_BACKEND}) - set(_OS_PREFIX ${QCPERF_BACKEND_OS_PREFIX_${_BACKEND}}) - set(QCPERF_ENABLED_${_OS_PREFIX}_${_BACKEND} ON) - message(STATUS "Backend enabled (user selection): ${_OS_PREFIX}_${_BACKEND}") - else() - set(QCPERF_ENABLED_${_BACKEND} ON) - message(STATUS "Backend enabled (user selection): ${_BACKEND}") - endif() - else() - message(WARNING "Backend '${_BACKEND}' is not supported on this platform — skipping.") - endif() - endforeach() -else() - # Default: enable all platform-supported backends - foreach(_BACKEND ${QCPERF_PLATFORM_SUPPORTED_BACKENDS}) - if(DEFINED QCPERF_BACKEND_OS_PREFIX_${_BACKEND}) - set(_OS_PREFIX ${QCPERF_BACKEND_OS_PREFIX_${_BACKEND}}) - set(QCPERF_ENABLED_${_OS_PREFIX}_${_BACKEND} ON) - message(STATUS "Backend enabled (platform default): ${_OS_PREFIX}_${_BACKEND}") - else() - set(QCPERF_ENABLED_${_BACKEND} ON) - message(STATUS "Backend enabled (platform default): ${_BACKEND}") - endif() - endforeach() -endif() - -# Propagate enabled backends as compile definitions for use in source code. -# This loop is automatic — no changes needed here when adding a new backend; -# just add the backend name to the appropriate platform section above. -foreach(_BACKEND ${QCPERF_PLATFORM_SUPPORTED_BACKENDS}) - if(DEFINED QCPERF_BACKEND_OS_PREFIX_${_BACKEND}) - set(_OS_PREFIX ${QCPERF_BACKEND_OS_PREFIX_${_BACKEND}}) - set(_COMPILE_FLAG QCPERF_ENABLED_${_OS_PREFIX}_${_BACKEND}) - else() - set(_COMPILE_FLAG QCPERF_ENABLED_${_BACKEND}) - endif() - if(${_COMPILE_FLAG}) - add_compile_definitions(${_COMPILE_FLAG}) - endif() -endforeach() - -message(STATUS "") -message(STATUS "Enabled backends: ${QCPERF_PLATFORM_SUPPORTED_BACKENDS}") - -# Linux-specific configurations -if(QCPERF_OS_LINUX) - # Enable Linux-specific definitions - add_compile_definitions(QCPERF_PLATFORM_LINUX) - - # Linux-specific compiler flags - if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) - # GCC-specific flags - add_compile_options( - -Wall - -Wextra - -Wpedantic - -fPIC - ) - - # Enable additional warnings - add_compile_options( - -Wcast-align - -Wunused - -Wconversion - -Wsign-conversion - ) - elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # Clang-specific flags - add_compile_options( - -Wall - -Wextra - -Wpedantic - -fPIC - ) - endif() - - # ARM64 specific configurations - if(QCPERF_PLATFORM_LINUX_ARM64) - message(STATUS "Configuring for Linux ARM64") - - # Add ARM64-specific compile definitions - add_compile_definitions(LINUX_ARM64) - - if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) - # Enable ARM64 specific optimizations - add_compile_options(-march=armv8-a) - endif() - endif() - - if(QCPERF_PLATFORM_LINUX_X86_64) - message(STATUS "Configuring for Linux x86_64") - - # Add x86_64-specific compile definitions if needed - add_compile_definitions(LINUX_X86_64) - endif() - - set(_OS_M "m") - if(QCPERF_OS_ANDROID) - set(_OS_THREAD "") - else() - set(_OS_THREAD "pthread") - endif() - - message(STATUS "Linux platform configuration loaded") -endif() - -# ============================================================================ -# Compiler Configuration -# ============================================================================ -# Configure compiler settings that apply across all platforms: -# - C language standard -# - Build type (Debug/Release) specific optimizations -# - Warning levels and error handling -# - Code generation options - -# Set C standard -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) - -# Set C++ standard -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Set default build type if not specified -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) -endif() - -message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") - -# Common compiler flags for all platforms -if(MSVC) - # MSVC-specific compiler flags - add_compile_options( - /W4 # Warning level 4 - /WX- # Don't treat warnings as errors (can be changed to /WX if needed) - /MP # Multi-processor compilation - /EHsc # Standard C++ exception handling - /permissive- # Enforce standards conformance - ) - - # Debug-specific flags - add_compile_options( - $<$:/Zi> # Generate debug info - $<$:/Od> # Disable optimization - $<$:/RTC1> # Runtime error checks - ) - - # Release-specific flags - add_compile_options( - $<$:/O2> # Optimize for speed - $<$:/Oi> # Enable intrinsic functions - $<$:/GL> # Whole program optimization - ) - add_link_options( - $<$:/LTCG> # Link-time code generation - ) - -else() - # GCC/Clang compiler flags - add_compile_options( - -Wall - -Wextra - -Wpedantic - ) - - # Debug-specific flags - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_compile_options( - -g # Generate debug info - -O0 # No optimization - ) - endif() - - # Release-specific flags - if(CMAKE_BUILD_TYPE STREQUAL "Release") - add_compile_options( - -O3 # Optimize for speed - ) - endif() -endif() - -# Enable Position Independent Code for all targets -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - -message(STATUS "Compiler configuration loaded") - -# ============================================================================ -# Platform Types -# ============================================================================ -# Check for standard headers and define fallback types if needed. -# This ensures consistent type definitions across different platforms -# and compiler implementations. - -# When cross-compiling, skip host-based header probes and assume standard -# C11 headers are present in the cross-toolchain (ARM GNU Toolchain ships -# all standard headers). When building natively, probe the host compiler. -if(NOT CMAKE_CROSSCOMPILING) - include(CheckIncludeFile) - CHECK_INCLUDE_FILE(stdbool.h HAVE_STDBOOL_H) - CHECK_INCLUDE_FILE(stdint.h HAVE_STDINT_H) - CHECK_INCLUDE_FILE(inttypes.h HAVE_INTTYPES_H) - CHECK_INCLUDE_FILE(stddef.h HAVE_STDDEF_H) -else() - # Cross-compiling: ARM GNU Toolchain ships all standard C11 headers - message(STATUS "Cross-compiling: skipping host header checks, assuming standard C11 headers present") - set(HAVE_STDBOOL_H TRUE) - set(HAVE_STDINT_H TRUE) - set(HAVE_INTTYPES_H TRUE) - set(HAVE_STDDEF_H TRUE) -endif() - -if(HAVE_STDBOOL_H) - add_compile_definitions(HAVE_STDBOOL_H) -else() - # Define bool type if stdbool.h is not available - add_compile_definitions( - bool=int - true=1 - false=0 - ) -endif() - -# Add appropriate definitions based on available headers -if(HAVE_STDINT_H) - add_compile_definitions(HAVE_STDINT_H) -endif() - -if(HAVE_INTTYPES_H) - add_compile_definitions(HAVE_INTTYPES_H) -endif() - -if(HAVE_STDDEF_H) - add_compile_definitions(HAVE_STDDEF_H) -endif() - -message(STATUS "Platform types configuration loaded") +# ============================================================================ +# QcPerf - Combined Build Configuration +# ============================================================================ +# This file centralizes all build configuration for the QcPerf library: +# - Platform detection (OS and architecture) +# - Platform-specific compiler flags and definitions +# - Compiler configuration and optimization settings +# - Platform types and standard header checks +# +# It is designed to support multiple platforms (Windows, Linux) and +# architectures (x86_64, ARM64) with a focus on Windows ARM64 support. +# ============================================================================ + +# ============================================================================ +# Platform Detection +# ============================================================================ +# Detect operating system and architecture to set appropriate build flags +# and definitions. This enables conditional compilation for platform-specific +# code throughout the project. + +# Detect OS +if(WIN32) + set(QCPERF_OS_WINDOWS TRUE) + message(STATUS "Detected Windows platform") +elseif(ANDROID) + set(QCPERF_OS_LINUX TRUE) + set(QCPERF_OS_ANDROID TRUE) + message(STATUS "Detected Android platform") +elseif(UNIX) + set(QCPERF_OS_LINUX TRUE) + message(STATUS "Detected Linux platform") +else() + message(WARNING "Unknown platform detected") +endif() + +# Detect architecture +# For Visual Studio, we need to check CMAKE_VS_PLATFORM_NAME +if(MSVC AND CMAKE_VS_PLATFORM_NAME MATCHES "ARM64") + set(QCPERF_ARCH_ARM64 TRUE) + # Override CMAKE_SYSTEM_PROCESSOR for Visual Studio ARM64 builds + set(CMAKE_SYSTEM_PROCESSOR "ARM64") + message(STATUS "Detected ARM64 architecture (Visual Studio)") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64") + set(QCPERF_ARCH_X86_64 TRUE) + message(STATUS "Detected x86_64 architecture") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|arm64") + set(QCPERF_ARCH_ARM64 TRUE) + message(STATUS "Detected ARM64 architecture") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i686|i386") + set(QCPERF_ARCH_X86 TRUE) + message(STATUS "Detected x86 architecture") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "armv7|armhf") + set(QCPERF_ARCH_ARM32 TRUE) + message(STATUS "Detected ARM32 architecture") +else() + message(STATUS "Architecture detection using CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") + if(DEFINED CMAKE_VS_PLATFORM_NAME) + message(STATUS "Visual Studio platform name: ${CMAKE_VS_PLATFORM_NAME}") + endif() + message(WARNING "Unknown architecture - defaulting to x86_64") + set(QCPERF_ARCH_X86_64 TRUE) +endif() + +# Set combined platform variables for convenience +if(QCPERF_OS_WINDOWS AND QCPERF_ARCH_ARM64) + set(QCPERF_PLATFORM_WINDOWS_ARM64 TRUE) +elseif(QCPERF_OS_WINDOWS AND QCPERF_ARCH_X86_64) + set(QCPERF_PLATFORM_WINDOWS_X86_64 TRUE) +elseif(QCPERF_OS_LINUX AND QCPERF_ARCH_ARM64) + set(QCPERF_PLATFORM_LINUX_ARM64 TRUE) +elseif(QCPERF_OS_LINUX AND QCPERF_ARCH_X86_64) + set(QCPERF_PLATFORM_LINUX_X86_64 TRUE) +endif() + +# Print platform summary +message(STATUS "QcPerf Platform Configuration:") +message(STATUS " OS: ${CMAKE_SYSTEM_NAME}") +message(STATUS " Architecture: ${CMAKE_SYSTEM_PROCESSOR}") +if(CMAKE_CROSSCOMPILING) + message(STATUS " Cross-compiling: YES (host=${CMAKE_HOST_SYSTEM_PROCESSOR} -> target=${CMAKE_SYSTEM_PROCESSOR})") + message(STATUS " C compiler: ${CMAKE_C_COMPILER}") +else() + message(STATUS " Cross-compiling: NO (native build)") +endif() + +# ============================================================================ +# Platform-Specific Configurations +# ============================================================================ +# Apply platform-specific compiler flags, definitions, and optimizations +# based on the detected OS and architecture combinations. + +# Windows-specific configurations +if(QCPERF_OS_WINDOWS) + # Enable Windows-specific definitions + add_compile_definitions(QCPERF_PLATFORM_WINDOWS) + + # Windows-specific compiler flags + if(MSVC) + # Enable multi-processor compilation + add_compile_options(/MP) + + # Warning level + add_compile_options(/W4) + + # Disable specific warnings if needed + # add_compile_options(/wd4100) # Unused parameter + + # Enable additional security features + add_compile_definitions( + _CRT_SECURE_NO_WARNINGS + _SCL_SECURE_NO_WARNINGS + ) + endif() + + # ARM64 specific configurations + if(QCPERF_PLATFORM_WINDOWS_ARM64) + message(STATUS "Configuring for Windows ARM64") + + # Add ARM64-specific compile definitions + add_compile_definitions(WIN32_ARM64) + endif() + + # x86_64 specific configurations + if(QCPERF_PLATFORM_WINDOWS_X86_64) + message(STATUS "Configuring for Windows x86_64") + endif() + + message(STATUS "Windows platform configuration loaded") +endif() + +# ============================================================================ +# Backend Selection +# ============================================================================ +# Determine which backends are supported on the current platform. +# For each OS-specific backend, QCPERF_BACKEND_OS_PREFIX_ is set to the +# OS qualifier (e.g. QCOM_LINUX, WOS) so the compile definition becomes +# QCPERF_ENABLED__. DUMMY has no OS prefix and remains QCPERF_ENABLED_DUMMY. +set(QCPERF_PLATFORM_SUPPORTED_BACKENDS "DUMMY") + +if(QCPERF_PLATFORM_LINUX_ARM64) + set(_OS_PREFIX "QCOM_LINUX") + foreach(_BACKEND CPU NPU THERMAL GPU) + list(APPEND QCPERF_PLATFORM_SUPPORTED_BACKENDS ${_BACKEND}) + set(QCPERF_BACKEND_OS_PREFIX_${_BACKEND} ${_OS_PREFIX}) + endforeach() +endif() + +if(QCPERF_PLATFORM_WINDOWS_ARM64) + set(_OS_PREFIX "WOS") + foreach(_BACKEND THERMAL POWER CPU NPU) + list(APPEND QCPERF_PLATFORM_SUPPORTED_BACKENDS ${_BACKEND}) + set(QCPERF_BACKEND_OS_PREFIX_${_BACKEND} ${_OS_PREFIX}) + endforeach() +endif() + +# If empty (default), all platform-supported backends are enabled. +set(BACKENDS "" CACHE STRING + "Semicolon-separated list of backends to enable (e.g. \"DUMMY;CPU;NPU\"). \ +If empty, all platform-supported backends are enabled.") + +if(BACKENDS) + # User specified an explicit list — enable only those supported on this platform + foreach(_BACKEND_INPUT ${BACKENDS}) + string(TOUPPER "${_BACKEND_INPUT}" _BACKEND) + if(${_BACKEND} IN_LIST QCPERF_PLATFORM_SUPPORTED_BACKENDS) + if(DEFINED QCPERF_BACKEND_OS_PREFIX_${_BACKEND}) + set(_OS_PREFIX ${QCPERF_BACKEND_OS_PREFIX_${_BACKEND}}) + set(QCPERF_ENABLED_${_OS_PREFIX}_${_BACKEND} ON) + message(STATUS "Backend enabled (user selection): ${_OS_PREFIX}_${_BACKEND}") + else() + set(QCPERF_ENABLED_${_BACKEND} ON) + message(STATUS "Backend enabled (user selection): ${_BACKEND}") + endif() + else() + message(WARNING "Backend '${_BACKEND}' is not supported on this platform — skipping.") + endif() + endforeach() +else() + # Default: enable all platform-supported backends + foreach(_BACKEND ${QCPERF_PLATFORM_SUPPORTED_BACKENDS}) + if(DEFINED QCPERF_BACKEND_OS_PREFIX_${_BACKEND}) + set(_OS_PREFIX ${QCPERF_BACKEND_OS_PREFIX_${_BACKEND}}) + set(QCPERF_ENABLED_${_OS_PREFIX}_${_BACKEND} ON) + message(STATUS "Backend enabled (platform default): ${_OS_PREFIX}_${_BACKEND}") + else() + set(QCPERF_ENABLED_${_BACKEND} ON) + message(STATUS "Backend enabled (platform default): ${_BACKEND}") + endif() + endforeach() +endif() + +# Propagate enabled backends as compile definitions for use in source code. +# This loop is automatic — no changes needed here when adding a new backend; +# just add the backend name to the appropriate platform section above. +foreach(_BACKEND ${QCPERF_PLATFORM_SUPPORTED_BACKENDS}) + if(DEFINED QCPERF_BACKEND_OS_PREFIX_${_BACKEND}) + set(_OS_PREFIX ${QCPERF_BACKEND_OS_PREFIX_${_BACKEND}}) + set(_COMPILE_FLAG QCPERF_ENABLED_${_OS_PREFIX}_${_BACKEND}) + else() + set(_COMPILE_FLAG QCPERF_ENABLED_${_BACKEND}) + endif() + if(${_COMPILE_FLAG}) + add_compile_definitions(${_COMPILE_FLAG}) + endif() +endforeach() + +message(STATUS "") +message(STATUS "Enabled backends: ${QCPERF_PLATFORM_SUPPORTED_BACKENDS}") + +# Linux-specific configurations +if(QCPERF_OS_LINUX) + # Enable Linux-specific definitions + add_compile_definitions(QCPERF_PLATFORM_LINUX) + + # Linux-specific compiler flags + if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + # GCC-specific flags + add_compile_options( + -Wall + -Wextra + -Wpedantic + -fPIC + ) + + # Enable additional warnings + add_compile_options( + -Wcast-align + -Wunused + -Wconversion + -Wsign-conversion + ) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Clang-specific flags + add_compile_options( + -Wall + -Wextra + -Wpedantic + -fPIC + ) + endif() + + # ARM64 specific configurations + if(QCPERF_PLATFORM_LINUX_ARM64) + message(STATUS "Configuring for Linux ARM64") + + # Add ARM64-specific compile definitions + add_compile_definitions(LINUX_ARM64) + + if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + # Enable ARM64 specific optimizations + add_compile_options(-march=armv8-a) + endif() + endif() + + if(QCPERF_PLATFORM_LINUX_X86_64) + message(STATUS "Configuring for Linux x86_64") + + # Add x86_64-specific compile definitions if needed + add_compile_definitions(LINUX_X86_64) + endif() + + set(_OS_M "m") + if(QCPERF_OS_ANDROID) + set(_OS_THREAD "") + else() + set(_OS_THREAD "pthread") + endif() + + message(STATUS "Linux platform configuration loaded") +endif() + +# ============================================================================ +# Compiler Configuration +# ============================================================================ +# Configure compiler settings that apply across all platforms: +# - C language standard +# - Build type (Debug/Release) specific optimizations +# - Warning levels and error handling +# - Code generation options + +# Set C standard +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# Set C++ standard +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Set default build type if not specified +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) +endif() + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +# Common compiler flags for all platforms +if(MSVC) + # MSVC-specific compiler flags + add_compile_options( + /W4 # Warning level 4 + /WX- # Don't treat warnings as errors (can be changed to /WX if needed) + /MP # Multi-processor compilation + /EHsc # Standard C++ exception handling + /permissive- # Enforce standards conformance + ) + + # Debug-specific flags + add_compile_options( + $<$:/Zi> # Generate debug info + $<$:/Od> # Disable optimization + $<$:/RTC1> # Runtime error checks + ) + + # Release-specific flags + add_compile_options( + $<$:/O2> # Optimize for speed + $<$:/Oi> # Enable intrinsic functions + $<$:/GL> # Whole program optimization + ) + add_link_options( + $<$:/LTCG> # Link-time code generation + ) + +else() + # GCC/Clang compiler flags + add_compile_options( + -Wall + -Wextra + -Wpedantic + ) + + # Debug-specific flags + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_options( + -g # Generate debug info + -O0 # No optimization + ) + endif() + + # Release-specific flags + if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_compile_options( + -O3 # Optimize for speed + ) + endif() +endif() + +# Enable Position Independent Code for all targets +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +message(STATUS "Compiler configuration loaded") + +# ============================================================================ +# Platform Types +# ============================================================================ +# Check for standard headers and define fallback types if needed. +# This ensures consistent type definitions across different platforms +# and compiler implementations. + +# When cross-compiling, skip host-based header probes and assume standard +# C11 headers are present in the cross-toolchain (ARM GNU Toolchain ships +# all standard headers). When building natively, probe the host compiler. +if(NOT CMAKE_CROSSCOMPILING) + include(CheckIncludeFile) + CHECK_INCLUDE_FILE(stdbool.h HAVE_STDBOOL_H) + CHECK_INCLUDE_FILE(stdint.h HAVE_STDINT_H) + CHECK_INCLUDE_FILE(inttypes.h HAVE_INTTYPES_H) + CHECK_INCLUDE_FILE(stddef.h HAVE_STDDEF_H) +else() + # Cross-compiling: ARM GNU Toolchain ships all standard C11 headers + message(STATUS "Cross-compiling: skipping host header checks, assuming standard C11 headers present") + set(HAVE_STDBOOL_H TRUE) + set(HAVE_STDINT_H TRUE) + set(HAVE_INTTYPES_H TRUE) + set(HAVE_STDDEF_H TRUE) +endif() + +if(HAVE_STDBOOL_H) + add_compile_definitions(HAVE_STDBOOL_H) +else() + # Define bool type if stdbool.h is not available + add_compile_definitions( + bool=int + true=1 + false=0 + ) +endif() + +# Add appropriate definitions based on available headers +if(HAVE_STDINT_H) + add_compile_definitions(HAVE_STDINT_H) +endif() + +if(HAVE_INTTYPES_H) + add_compile_definitions(HAVE_INTTYPES_H) +endif() + +if(HAVE_STDDEF_H) + add_compile_definitions(HAVE_STDDEF_H) +endif() + +message(STATUS "Platform types configuration loaded")