diff --git a/CMakeLists.txt b/CMakeLists.txt index 072c9873..15ce1ec6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,7 +108,7 @@ IF(NOT CMAKE_SYSTEM_NAME) "Target operating system is not specified. " "cpuinfo will compile, but cpuinfo_initialize() will always fail.") SET(CPUINFO_SUPPORTED_PLATFORM FALSE) -ELSEIF(NOT CMAKE_SYSTEM_NAME MATCHES "^(Windows|WindowsStore|CYGWIN|MSYS|Darwin|Linux|Android|FreeBSD|Emscripten)$") +ELSEIF(NOT CMAKE_SYSTEM_NAME MATCHES "^(Windows|WindowsStore|CYGWIN|MSYS|Darwin|Linux|Android|FreeBSD|Emscripten|Generic)$") IF(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14" AND NOT IS_APPLE_OS) MESSAGE(WARNING "Target operating system \"${CMAKE_SYSTEM_NAME}\" is not supported in cpuinfo. " @@ -211,6 +211,13 @@ IF(CPUINFO_SUPPORTED_PLATFORM) ENDIF() ELSEIF(IS_APPLE_OS AND CPUINFO_TARGET_PROCESSOR MATCHES "arm64.*") LIST(APPEND CPUINFO_SRCS src/arm/mach/init.c) + ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Generic" AND CPUINFO_TARGET_PROCESSOR MATCHES "^(aarch64|arm64.*)$") + # "Generic" is CMake's name for a target without an OS, which covers both + # an RTOS (Zephyr, FreeRTOS) and no OS at all. Such a target runs at EL1 + # and reads the AArch64 ID registers directly. AArch64 only: the ID + # register layout below does not apply to AArch32. + LIST(APPEND CPUINFO_SRCS src/arm/baremetal/init.c) + SET(CPUINFO_BAREMETAL_INIT TRUE) ENDIF() IF(CMAKE_SYSTEM_NAME STREQUAL "Android") LIST(APPEND CPUINFO_SRCS @@ -264,6 +271,12 @@ ADD_LIBRARY(cpuinfo_internals STATIC ${CPUINFO_SRCS}) CPUINFO_TARGET_ENABLE_C99(cpuinfo) CPUINFO_TARGET_ENABLE_C99(cpuinfo_internals) CPUINFO_TARGET_RUNTIME_LIBRARY(cpuinfo) +IF(CPUINFO_BAREMETAL_INIT) + # Selects the bare-metal init path and its non-POSIX init guard, so cpuinfo + # builds in configurations without pthreads. + TARGET_COMPILE_DEFINITIONS(cpuinfo PRIVATE CPUINFO_BAREMETAL=1) + TARGET_COMPILE_DEFINITIONS(cpuinfo_internals PRIVATE CPUINFO_BAREMETAL=1) +ENDIF() IF(CMAKE_SYSTEM_NAME MATCHES "^(Windows|WindowsStore|CYGWIN|MSYS)$") # Target Windows 7+ API TARGET_COMPILE_DEFINITIONS(cpuinfo PRIVATE _WIN32_WINNT=0x0601 _CRT_SECURE_NO_WARNINGS) diff --git a/src/arm/baremetal/init.c b/src/arm/baremetal/init.c new file mode 100644 index 00000000..9599bffe --- /dev/null +++ b/src/arm/baremetal/init.c @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2026 BayLibre SAS + * SPDX-License-Identifier: BSD-2-Clause + * + * cpuinfo initialization for bare-metal AArch64 targets, i.e. an RTOS or no + * OS at all, where the code runs at EL1. + * + * ISA features are read straight out of the AArch64 ID registers rather than + * from /proc/cpuinfo or HWCAP. That is not merely an alternative to the Linux + * path, it is the only option here: there is no kernel publishing HWCAP, and + * conversely the ID registers are inaccessible from Linux userspace. Reading + * them requires EL1, which is exactly where an RTOS application runs. + * + * Nothing below is specific to one RTOS. The only external input is the core + * count, obtained through get_num_cpus() below; add a case there to support + * another RTOS. + * + * Topology is simplified to a single package with a single cluster. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(__ZEPHYR__) + +#include + +static inline uint32_t get_num_cpus(void) { + return (uint32_t)arch_num_cpus(); +} + +#else + +/* + * No RTOS-specific way to ask: assume a single core. SMP-capable ports should + * add a case above rather than change this default. + */ +#define get_num_cpus() 1u + +#endif + +struct cpuinfo_arm_isa cpuinfo_isa = {0}; + +static struct cpuinfo_package static_package = {{0}}; + +/* + * ID_AA64ISAR0_EL1 field definitions (4-bit fields). + */ +#define ISAR0_AES_SHIFT 4 +#define ISAR0_SHA1_SHIFT 8 +#define ISAR0_SHA2_SHIFT 12 +#define ISAR0_CRC32_SHIFT 16 +#define ISAR0_ATOMIC_SHIFT 20 +#define ISAR0_RDM_SHIFT 28 +#define ISAR0_DP_SHIFT 44 /* DotProduct (UDOT/SDOT) */ +#define ISAR0_FHM_SHIFT 48 + +/* + * ID_AA64ISAR1_EL1 field definitions. + */ +#define ISAR1_JSCVT_SHIFT 12 +#define ISAR1_FCMA_SHIFT 16 +#define ISAR1_I8MM_SHIFT 52 +#define ISAR1_BF16_SHIFT 44 + +/* + * ID_AA64PFR0_EL1 field definitions. + */ +#define PFR0_FP_SHIFT 16 +#define PFR0_ADVSIMD_SHIFT 20 +#define PFR0_SVE_SHIFT 32 + +/* + * ID_AA64PFR1_EL1 field definitions. + */ +#define PFR1_SME_SHIFT 24 + +/* + * ID_AA64SMFR0_EL1 field definitions (for SME sub-features). + */ +#define SMFR0_I16I32_BIT (1ULL << 52) +#define SMFR0_BI32I32_BIT (1ULL << 48) +#define SMFR0_B16B16_BIT (1ULL << 60) +#define SMFR0_F16F16_BIT (1ULL << 42) + +static inline uint64_t read_id_aa64isar0(void) { + uint64_t val; + __asm__ volatile("mrs %0, id_aa64isar0_el1" : "=r"(val)); + return val; +} + +static inline uint64_t read_id_aa64isar1(void) { + uint64_t val; + __asm__ volatile("mrs %0, id_aa64isar1_el1" : "=r"(val)); + return val; +} + +static inline uint64_t read_id_aa64pfr0(void) { + uint64_t val; + __asm__ volatile("mrs %0, id_aa64pfr0_el1" : "=r"(val)); + return val; +} + +static inline uint64_t read_id_aa64pfr1(void) { + uint64_t val; + __asm__ volatile("mrs %0, id_aa64pfr1_el1" : "=r"(val)); + return val; +} + +static inline uint64_t read_midr(void) { + uint64_t val; + __asm__ volatile("mrs %0, midr_el1" : "=r"(val)); + return val; +} + +#define FIELD(reg, shift) (((reg) >> (shift)) & 0xf) + +static void detect_isa_features(void) { + const uint64_t isar0 = read_id_aa64isar0(); + const uint64_t isar1 = read_id_aa64isar1(); + const uint64_t pfr0 = read_id_aa64pfr0(); + const uint64_t pfr1 = read_id_aa64pfr1(); + + /* ID_AA64ISAR0_EL1 */ + cpuinfo_isa.aes = FIELD(isar0, ISAR0_AES_SHIFT) >= 1; + cpuinfo_isa.pmull = FIELD(isar0, ISAR0_AES_SHIFT) >= 2; + cpuinfo_isa.sha1 = FIELD(isar0, ISAR0_SHA1_SHIFT) >= 1; + cpuinfo_isa.sha2 = FIELD(isar0, ISAR0_SHA2_SHIFT) >= 1; + cpuinfo_isa.crc32 = FIELD(isar0, ISAR0_CRC32_SHIFT) >= 1; + cpuinfo_isa.atomics = FIELD(isar0, ISAR0_ATOMIC_SHIFT) >= 2; + cpuinfo_isa.rdm = FIELD(isar0, ISAR0_RDM_SHIFT) >= 1; + cpuinfo_isa.dot = FIELD(isar0, ISAR0_DP_SHIFT) >= 1; + cpuinfo_isa.fhm = FIELD(isar0, ISAR0_FHM_SHIFT) >= 1; + + /* ID_AA64ISAR1_EL1 */ + cpuinfo_isa.jscvt = FIELD(isar1, ISAR1_JSCVT_SHIFT) >= 1; + cpuinfo_isa.fcma = FIELD(isar1, ISAR1_FCMA_SHIFT) >= 1; + cpuinfo_isa.i8mm = FIELD(isar1, ISAR1_I8MM_SHIFT) >= 1; + cpuinfo_isa.bf16 = FIELD(isar1, ISAR1_BF16_SHIFT) >= 1; + + /* ID_AA64PFR0_EL1: FP16 arithmetic requires both FP and AdvSIMD + * fields to report half-precision support (value >= 1). */ + const uint32_t fp_field = FIELD(pfr0, PFR0_FP_SHIFT); + const uint32_t advsimd_field = FIELD(pfr0, PFR0_ADVSIMD_SHIFT); + cpuinfo_isa.fp16arith = (fp_field >= 1 && fp_field != 0xf) && (advsimd_field >= 1 && advsimd_field != 0xf); + + /* SVE */ + cpuinfo_isa.sve = FIELD(pfr0, PFR0_SVE_SHIFT) >= 1; + if (cpuinfo_isa.sve) { + /* SVE2: indicated by ID_AA64ZFR0_EL1.SVEver >= 1 */ + uint64_t zfr0; + __asm__ volatile("mrs %0, s3_0_c0_c4_4" : "=r"(zfr0)); + cpuinfo_isa.sve2 = (zfr0 & 0xf) >= 1; + + /* SVE vector length in bytes. */ + uint64_t vl_bytes; + __asm__ volatile("rdvl %0, #1" : "=r"(vl_bytes)); + cpuinfo_isa.svelen = (uint32_t)vl_bytes; + } + + /* ID_AA64PFR1_EL1: SME */ + const uint32_t sme_field = FIELD(pfr1, PFR1_SME_SHIFT); + cpuinfo_isa.sme = sme_field >= 1; + cpuinfo_isa.sme2 = sme_field >= 2; + cpuinfo_isa.sme2p1 = sme_field >= 3; + + if (cpuinfo_isa.sme) { + uint64_t smfr0; + __asm__ volatile("mrs %0, s3_0_c0_c4_5" : "=r"(smfr0)); + cpuinfo_isa.sme_i16i32 = !!(smfr0 & SMFR0_I16I32_BIT); + cpuinfo_isa.sme_bi32i32 = !!(smfr0 & SMFR0_BI32I32_BIT); + cpuinfo_isa.sme_b16b16 = !!(smfr0 & SMFR0_B16B16_BIT); + cpuinfo_isa.sme_f16f16 = !!(smfr0 & SMFR0_F16F16_BIT); + + /* + * SME streaming vector length may differ from SVE vector + * length but reading it requires entering streaming mode + * (SMSTART/SMSTOP) which has side effects. Default to + * SVE length as a reasonable approximation. + */ + cpuinfo_isa.smelen = cpuinfo_isa.svelen; + } +} + +void cpuinfo_arm_baremetal_init(void) { + struct cpuinfo_processor* processors = NULL; + struct cpuinfo_core* cores = NULL; + struct cpuinfo_cluster* clusters = NULL; + struct cpuinfo_uarch_info* uarchs = NULL; + struct cpuinfo_cache* l1i = NULL; + struct cpuinfo_cache* l1d = NULL; + struct cpuinfo_cache* l2 = NULL; + + const uint32_t core_count = get_num_cpus(); + const uint32_t cluster_count = 1; + + detect_isa_features(); + + processors = calloc(core_count, sizeof(struct cpuinfo_processor)); + cores = calloc(core_count, sizeof(struct cpuinfo_core)); + clusters = calloc(cluster_count, sizeof(struct cpuinfo_cluster)); + uarchs = calloc(cluster_count, sizeof(struct cpuinfo_uarch_info)); + l1i = calloc(core_count, sizeof(struct cpuinfo_cache)); + l1d = calloc(core_count, sizeof(struct cpuinfo_cache)); + l2 = calloc(cluster_count, sizeof(struct cpuinfo_cache)); + + if (!processors || !cores || !clusters || !uarchs || !l1i || !l1d || !l2) { + cpuinfo_log_error("failed to allocate cpuinfo structures"); + goto cleanup; + } + + const uint64_t midr = read_midr(); + enum cpuinfo_vendor vendor = cpuinfo_vendor_unknown; + enum cpuinfo_uarch uarch = cpuinfo_uarch_unknown; + cpuinfo_arm_decode_vendor_uarch((uint32_t)midr, &vendor, &uarch); + + strncpy(static_package.name, "ARM AArch64", CPUINFO_PACKAGE_NAME_MAX); + static_package.processor_count = core_count; + static_package.core_count = core_count; + static_package.cluster_count = cluster_count; + + clusters[0] = (struct cpuinfo_cluster){ + .processor_start = 0, + .processor_count = core_count, + .core_start = 0, + .core_count = core_count, + .cluster_id = 0, + .package = &static_package, + .vendor = vendor, + .uarch = uarch, + }; + + for (uint32_t i = 0; i < core_count; i++) { + processors[i] = (struct cpuinfo_processor){ + .smt_id = 0, + .core = cores + i, + .cluster = clusters, + .package = &static_package, + .cache.l1i = l1i + i, + .cache.l1d = l1d + i, + .cache.l2 = l2, + }; + + cores[i] = (struct cpuinfo_core){ + .processor_start = i, + .processor_count = 1, + .core_id = i, + .cluster = clusters, + .package = &static_package, + .vendor = vendor, + .uarch = uarch, + .midr = (uint32_t)midr, + }; + + /* + * TODO: read CCSIDR_EL1 (via CSSELR_EL1 level select) for + * actual cache geometry. These are conservative defaults + * typical for Cortex-A class cores. + */ + l1i[i] = (struct cpuinfo_cache){ + .size = 64 * 1024, + .associativity = 4, + .line_size = 64, + .processor_start = i, + .processor_count = 1, + }; + + l1d[i] = (struct cpuinfo_cache){ + .size = 64 * 1024, + .associativity = 4, + .line_size = 64, + .processor_start = i, + .processor_count = 1, + }; + } + + l2[0] = (struct cpuinfo_cache){ + .size = 512 * 1024, + .associativity = 8, + .line_size = 64, + .processor_start = 0, + .processor_count = core_count, + }; + + /* Commit */ + cpuinfo_processors = processors; + cpuinfo_cores = cores; + cpuinfo_clusters = clusters; + cpuinfo_packages = &static_package; + + cpuinfo_cache[cpuinfo_cache_level_1i] = l1i; + cpuinfo_cache[cpuinfo_cache_level_1d] = l1d; + cpuinfo_cache[cpuinfo_cache_level_2] = l2; + + cpuinfo_cache_count[cpuinfo_cache_level_1i] = core_count; + cpuinfo_cache_count[cpuinfo_cache_level_1d] = core_count; + cpuinfo_cache_count[cpuinfo_cache_level_2] = cluster_count; + + uarchs[0] = (struct cpuinfo_uarch_info){ + .uarch = uarch, + .processor_count = core_count, + .core_count = core_count, + }; + cpuinfo_uarchs = uarchs; + cpuinfo_uarchs_count = cluster_count; + + cpuinfo_processors_count = core_count; + cpuinfo_cores_count = core_count; + cpuinfo_clusters_count = cluster_count; + cpuinfo_packages_count = 1; + + cpuinfo_max_cache_size = 8 * 1024 * 1024; + + cpuinfo_is_initialized = true; + + processors = NULL; + cores = NULL; + clusters = NULL; + uarchs = NULL; + l1i = l1d = l2 = NULL; + +cleanup: + free(processors); + free(cores); + free(clusters); + free(uarchs); + free(l1i); + free(l1d); + free(l2); +} diff --git a/src/cpuinfo/internal-api.h b/src/cpuinfo/internal-api.h index d84b26a8..df72c41b 100644 --- a/src/cpuinfo/internal-api.h +++ b/src/cpuinfo/internal-api.h @@ -59,6 +59,7 @@ CPUINFO_PRIVATE BOOL CALLBACK cpuinfo_x86_windows_init(PINIT_ONCE init_once, PVO #endif CPUINFO_PRIVATE void cpuinfo_arm_mach_init(void); CPUINFO_PRIVATE void cpuinfo_arm_linux_init(void); +CPUINFO_PRIVATE void cpuinfo_arm_baremetal_init(void); CPUINFO_PRIVATE void cpuinfo_riscv_linux_init(void); CPUINFO_PRIVATE void cpuinfo_emscripten_init(void); diff --git a/src/init.c b/src/init.c index 81d5721c..158b7fc6 100644 --- a/src/init.c +++ b/src/init.c @@ -1,5 +1,7 @@ #if defined(_WIN32) || defined(__CYGWIN__) #include +#elif defined(CPUINFO_BAREMETAL) +/* No threading library assumed, see the init guard below. */ #elif !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__) #include #endif @@ -14,6 +16,13 @@ #if defined(_WIN32) || defined(__CYGWIN__) static INIT_ONCE init_guard = INIT_ONCE_STATIC_INIT; +#elif defined(CPUINFO_BAREMETAL) +/* + * Bare-metal targets need not have POSIX threads: initialization happens + * before secondary cores are released, so a plain flag is enough and this + * keeps cpuinfo usable in configurations built without pthreads. + */ +static bool init_guard = false; #elif !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__) static pthread_once_t init_guard = PTHREAD_ONCE_INIT; #else @@ -40,6 +49,11 @@ bool CPUINFO_ABI cpuinfo_initialize(void) { pthread_once(&init_guard, &cpuinfo_arm_mach_init); #elif defined(_WIN32) InitOnceExecuteOnce(&init_guard, &cpuinfo_arm_windows_init, NULL, NULL); +#elif defined(CPUINFO_BAREMETAL) + if (!init_guard) { + cpuinfo_arm_baremetal_init(); + init_guard = true; + } #else cpuinfo_log_error("operating system is not supported in cpuinfo"); #endif diff --git a/src/log.c b/src/log.c index 2f85128d..e6eabe28 100644 --- a/src/log.c +++ b/src/log.c @@ -37,6 +37,12 @@ #elif defined(__hexagon__) #define CPUINFO_LOG_NEWLINE_LENGTH 1 +#define CPUINFO_LOG_STDERR 0 +#define CPUINFO_LOG_STDOUT 0 +#elif defined(CPUINFO_BAREMETAL) +/* No file descriptors: output goes through printf(), see below. */ +#define CPUINFO_LOG_NEWLINE_LENGTH 1 + #define CPUINFO_LOG_STDERR 0 #define CPUINFO_LOG_STDOUT 0 #else @@ -112,6 +118,10 @@ static void cpuinfo_vlog( NULL); #elif defined(__hexagon__) qurt_printf("%s", out_buffer); +#elif defined(CPUINFO_BAREMETAL) + out_buffer[prefix_length + format_length] = '\n'; + out_buffer[prefix_length + format_length + 1] = '\0'; + printf("%s", out_buffer); #else out_buffer[prefix_length + format_length] = '\n';