|
#include <stdbool.h> |
|
#include <stdint.h> |
|
#include <stdlib.h> |
|
#include <stddef.h> |
|
#include <string.h> |
|
|
|
#include <linux/api.h> |
|
#include <arm/linux/api.h> |
|
#include <arm/midr.h> |
|
#include <cpuinfo/log.h> |
|
|
|
|
|
|
|
|
|
|
|
#define BUFFER_SIZE 1024 |
|
|
|
|
|
static uint32_t parse_processor_number( |
|
const char* processor_start, |
|
const char* processor_end) |
|
{ |
|
const size_t processor_length = (size_t) (processor_end - processor_start); |
|
|
|
if (processor_length == 0) { |
|
cpuinfo_log_warning("Processor number in /proc/cpuinfo is ignored: string is empty"); |
|
return 0; |
|
} |
|
|
|
uint32_t processor_number = 0; |
|
for (const char* digit_ptr = processor_start; digit_ptr != processor_end; digit_ptr++) { |
|
const uint32_t digit = (uint32_t) (*digit_ptr - '0'); |
|
if (digit > 10) { |
|
cpuinfo_log_warning("non-decimal suffix %.*s in /proc/cpuinfo processor number is ignored", |
|
(int) (processor_end - digit_ptr), digit_ptr); |
|
break; |
|
} |
|
|
|
processor_number = processor_number * 10 + digit; |
|
} |
|
|
|
return processor_number; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void parse_features( |
|
const char* features_start, |
|
const char* features_end, |
|
struct cpuinfo_arm_linux_processor processor[restrict static 1]) |
|
{ |
|
const char* feature_start = features_start; |
|
const char* feature_end; |
|
|
|
|
|
processor->flags |= CPUINFO_ARM_LINUX_VALID_FEATURES | CPUINFO_ARM_LINUX_VALID_PROCESSOR; |
|
|
|
do { |
|
feature_end = feature_start + 1; |
|
for (; feature_end != features_end; feature_end++) { |
|
if (*feature_end == ' ') { |
|
break; |
|
} |
|
} |
|
const size_t feature_length = (size_t) (feature_end - feature_start); |
|
|
|
switch (feature_length) { |
|
case 2: |
|
if (memcmp(feature_start, "fp", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_FP; |
|
#endif |
|
#if CPUINFO_ARCH_ARM |
|
} else if (memcmp(feature_start, "wp", feature_length) == 0) { |
|
|
|
|
|
|
|
|
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_SWP; |
|
#endif |
|
} else { |
|
goto unexpected; |
|
} |
|
break; |
|
case 3: |
|
if (memcmp(feature_start, "aes", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM |
|
processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_AES; |
|
#elif CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_AES; |
|
#endif |
|
#if CPUINFO_ARCH_ARM |
|
} else if (memcmp(feature_start, "swp", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_SWP; |
|
} else if (memcmp(feature_start, "fpa", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_FPA; |
|
} else if (memcmp(feature_start, "vfp", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_VFP; |
|
} else if (memcmp(feature_start, "tls", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_TLS; |
|
#endif |
|
} else { |
|
goto unexpected; |
|
} |
|
break; |
|
case 4: |
|
if (memcmp(feature_start, "sha1", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM |
|
processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_SHA1; |
|
#elif CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_SHA1; |
|
#endif |
|
} else if (memcmp(feature_start, "sha2", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM |
|
processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_SHA2; |
|
#elif CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_SHA2; |
|
#endif |
|
} else if (memcmp(feature_start, "fphp", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_FPHP; |
|
#endif |
|
} else if (memcmp(feature_start, "fcma", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_FCMA; |
|
#endif |
|
} else if (memcmp(feature_start, "i8mm", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_I8MM; |
|
#endif |
|
#if CPUINFO_ARCH_ARM |
|
} else if (memcmp(feature_start, "half", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_HALF; |
|
} else if (memcmp(feature_start, "edsp", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_EDSP; |
|
} else if (memcmp(feature_start, "java", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_JAVA; |
|
} else if (memcmp(feature_start, "neon", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_NEON; |
|
} else if (memcmp(feature_start, "lpae", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_LPAE; |
|
} else if (memcmp(feature_start, "tlsi", feature_length) == 0) { |
|
|
|
|
|
|
|
|
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_TLS; |
|
#endif |
|
} else { |
|
goto unexpected; |
|
} |
|
break; |
|
case 5: |
|
if (memcmp(feature_start, "pmull", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM |
|
processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_PMULL; |
|
#elif CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_PMULL; |
|
#endif |
|
} else if (memcmp(feature_start, "crc32", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM |
|
processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_CRC32; |
|
#elif CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_CRC32; |
|
#endif |
|
} else if (memcmp(feature_start, "asimd", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_ASIMD; |
|
#endif |
|
} else if (memcmp(feature_start, "cpuid", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_CPUID; |
|
#endif |
|
} else if (memcmp(feature_start, "jscvt", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_JSCVT; |
|
#endif |
|
} else if (memcmp(feature_start, "lrcpc", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_LRCPC; |
|
#endif |
|
#if CPUINFO_ARCH_ARM |
|
} else if (memcmp(feature_start, "thumb", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_THUMB; |
|
} else if (memcmp(feature_start, "26bit", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_26BIT; |
|
} else if (memcmp(feature_start, "vfpv3", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_VFPV3; |
|
} else if (memcmp(feature_start, "vfpv4", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_VFPV4; |
|
} else if (memcmp(feature_start, "idiva", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_IDIVA; |
|
} else if (memcmp(feature_start, "idivt", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_IDIVT; |
|
#endif |
|
} else { |
|
goto unexpected; |
|
} |
|
break; |
|
#if CPUINFO_ARCH_ARM |
|
case 6: |
|
if (memcmp(feature_start, "iwmmxt", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_IWMMXT; |
|
} else if (memcmp(feature_start, "crunch", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_CRUNCH; |
|
} else if (memcmp(feature_start, "vfpd32", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_VFPD32; |
|
} else { |
|
goto unexpected; |
|
} |
|
break; |
|
#endif |
|
case 7: |
|
if (memcmp(feature_start, "evtstrm", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_EVTSTRM; |
|
} else if (memcmp(feature_start, "atomics", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_ATOMICS; |
|
#endif |
|
} else if (memcmp(feature_start, "asimdhp", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_ASIMDHP; |
|
#endif |
|
#if CPUINFO_ARCH_ARM |
|
} else if (memcmp(feature_start, "thumbee", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_THUMBEE; |
|
#endif |
|
} else { |
|
goto unexpected; |
|
} |
|
break; |
|
case 8: |
|
if (memcmp(feature_start, "asimdrdm", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_ASIMDRDM; |
|
#endif |
|
} else if (memcmp(feature_start, "asimdfhm", feature_length) == 0) { |
|
#if CPUINFO_ARCH_ARM64 |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_ASIMDFHM; |
|
#endif |
|
#if CPUINFO_ARCH_ARM |
|
} else if (memcmp(feature_start, "fastmult", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_FASTMULT; |
|
} else if (memcmp(feature_start, "vfpv3d16", feature_length) == 0) { |
|
processor->features |= CPUINFO_ARM_LINUX_FEATURE_VFPV3D16; |
|
#endif |
|
} else { |
|
goto unexpected; |
|
} |
|
break; |
|
default: |
|
unexpected: |
|
cpuinfo_log_warning("unexpected /proc/cpuinfo feature \"%.*s\" is ignored", |
|
(int) feature_length, feature_start); |
|
break; |
|
} |
|
feature_start = feature_end; |
|
for (; feature_start != features_end; feature_start++) { |
|
if (*feature_start != ' ') { |
|
break; |
|
} |
|
} |
|
} while (feature_start != feature_end); |
|
} |
|
|
|
static void parse_cpu_architecture( |
|
const char* cpu_architecture_start, |
|
const char* cpu_architecture_end, |
|
struct cpuinfo_arm_linux_processor processor[restrict static 1]) |
|
{ |
|
const size_t cpu_architecture_length = (size_t) (cpu_architecture_end - cpu_architecture_start); |
|
|
|
if (cpu_architecture_length == 7) { |
|
if (memcmp(cpu_architecture_start, "AArch64", cpu_architecture_length) == 0) { |
|
processor->midr = midr_set_architecture(processor->midr, UINT32_C(0xF)); |
|
processor->architecture_version = 8; |
|
processor->flags |= CPUINFO_ARM_LINUX_VALID_ARCHITECTURE | CPUINFO_ARM_LINUX_VALID_PROCESSOR; |
|
return; |
|
} |
|
} |
|
|
|
|
|
uint32_t architecture = 0; |
|
const char* cpu_architecture_ptr = cpu_architecture_start; |
|
for (; cpu_architecture_ptr != cpu_architecture_end; cpu_architecture_ptr++) { |
|
const uint32_t digit = (*cpu_architecture_ptr) - '0'; |
|
|
|
|
|
if (digit >= 10) { |
|
break; |
|
} |
|
|
|
architecture = architecture * 10 + digit; |
|
} |
|
|
|
if (cpu_architecture_ptr == cpu_architecture_start) { |
|
cpuinfo_log_warning("CPU architecture %.*s in /proc/cpuinfo is ignored due to non-digit at the beginning of the string", |
|
(int) cpu_architecture_length, cpu_architecture_start); |
|
} else { |
|
if (architecture != 0) { |
|
processor->architecture_version = architecture; |
|
processor->flags |= CPUINFO_ARM_LINUX_VALID_ARCHITECTURE | CPUINFO_ARM_LINUX_VALID_PROCESSOR; |
|
|
|
for (; cpu_architecture_ptr != cpu_architecture_end; cpu_architecture_ptr++) { |
|
const char feature = *cpu_architecture_ptr; |
|
switch (feature) { |
|
#if CPUINFO_ARCH_ARM |
|
case 'T': |
|
processor->architecture_flags |= CPUINFO_ARM_LINUX_ARCH_T; |
|
break; |
|
case 'E': |
|
processor->architecture_flags |= CPUINFO_ARM_LINUX_ARCH_E; |
|
break; |
|
case 'J': |
|
processor->architecture_flags |= CPUINFO_ARM_LINUX_ARCH_J; |
|
break; |
|
#endif |
|
case ' ': |
|
case '\t': |
|
|
|
break; |
|
default: |
|
cpuinfo_log_warning("skipped unknown architectural feature '%c' for ARMv%"PRIu32, |
|
feature, architecture); |
|
break; |
|
} |
|
} |
|
} else { |
|
cpuinfo_log_warning("CPU architecture %.*s in /proc/cpuinfo is ignored due to invalid value (0)", |
|
(int) cpu_architecture_length, cpu_architecture_start); |
|
} |
|
} |
|
|
|
uint32_t midr_architecture = UINT32_C(0xF); |
|
#if CPUINFO_ARCH_ARM |
|
switch (processor->architecture_version) { |
|
case 6: |
|
midr_architecture = UINT32_C(0x7); |
|
break; |
|
case 5: |
|
if ((processor->architecture_flags & CPUINFO_ARM_LINUX_ARCH_TEJ) == CPUINFO_ARM_LINUX_ARCH_TEJ) { |
|
midr_architecture = UINT32_C(0x6); |
|
} else if ((processor->architecture_flags & CPUINFO_ARM_LINUX_ARCH_TE) == CPUINFO_ARM_LINUX_ARCH_TE) { |
|
midr_architecture = UINT32_C(0x5); |
|
} else { |
|
midr_architecture = UINT32_C(0x4); |
|
} |
|
break; |
|
} |
|
#endif |
|
processor->midr = midr_set_architecture(processor->midr, midr_architecture); |
|
} |
|
|
|
static void parse_cpu_part( |
|
const char* cpu_part_start, |
|
const char* cpu_part_end, |
|
struct cpuinfo_arm_linux_processor processor[restrict static 1]) |
|
{ |
|
const size_t cpu_part_length = (size_t) (cpu_part_end - cpu_part_start); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (cpu_part_length < 3 || cpu_part_length > 5) { |
|
cpuinfo_log_warning("CPU part %.*s in /proc/cpuinfo is ignored due to unexpected length (%zu)", |
|
(int) cpu_part_length, cpu_part_start, cpu_part_length); |
|
return; |
|
} |
|
|
|
|
|
if (cpu_part_start[0] != '0' || cpu_part_start[1] != 'x') { |
|
cpuinfo_log_warning("CPU part %.*s in /proc/cpuinfo is ignored due to lack of 0x prefix", |
|
(int) cpu_part_length, cpu_part_start); |
|
return; |
|
} |
|
|
|
|
|
uint32_t cpu_part = 0; |
|
for (const char* digit_ptr = cpu_part_start + 2; digit_ptr != cpu_part_end; digit_ptr++) { |
|
const char digit_char = *digit_ptr; |
|
uint32_t digit; |
|
if (digit_char >= '0' && digit_char <= '9') { |
|
digit = digit_char - '0'; |
|
} else if ((uint32_t) (digit_char - 'A') < 6) { |
|
digit = 10 + (digit_char - 'A'); |
|
} else if ((uint32_t) (digit_char - 'a') < 6) { |
|
digit = 10 + (digit_char - 'a'); |
|
} else { |
|
cpuinfo_log_warning("CPU part %.*s in /proc/cpuinfo is ignored due to unexpected non-hex character %c at offset %zu", |
|
(int) cpu_part_length, cpu_part_start, digit_char, (size_t) (digit_ptr - cpu_part_start)); |
|
return; |
|
} |
|
cpu_part = cpu_part * 16 + digit; |
|
} |
|
|
|
processor->midr = midr_set_part(processor->midr, cpu_part); |
|
processor->flags |= CPUINFO_ARM_LINUX_VALID_PART | CPUINFO_ARM_LINUX_VALID_PROCESSOR; |
|
} |
|
|
|
static void parse_cpu_implementer( |
|
const char* cpu_implementer_start, |
|
const char* cpu_implementer_end, |
|
struct cpuinfo_arm_linux_processor processor[restrict static 1]) |
|
{ |
|
const size_t cpu_implementer_length = cpu_implementer_end - cpu_implementer_start; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch (cpu_implementer_length) { |
|
case 3: |
|
case 4: |
|
break; |
|
default: |
|
cpuinfo_log_warning("CPU implementer %.*s in /proc/cpuinfo is ignored due to unexpected length (%zu)", |
|
(int) cpu_implementer_length, cpu_implementer_start, cpu_implementer_length); |
|
return; |
|
} |
|
|
|
|
|
if (cpu_implementer_start[0] != '0' || cpu_implementer_start[1] != 'x') { |
|
cpuinfo_log_warning("CPU implementer %.*s in /proc/cpuinfo is ignored due to lack of 0x prefix", |
|
(int) cpu_implementer_length, cpu_implementer_start); |
|
return; |
|
} |
|
|
|
|
|
uint32_t cpu_implementer = 0; |
|
for (const char* digit_ptr = cpu_implementer_start + 2; digit_ptr != cpu_implementer_end; digit_ptr++) { |
|
const char digit_char = *digit_ptr; |
|
uint32_t digit; |
|
if (digit_char >= '0' && digit_char <= '9') { |
|
digit = digit_char - '0'; |
|
} else if ((uint32_t) (digit_char - 'A') < 6) { |
|
digit = 10 + (digit_char - 'A'); |
|
} else if ((uint32_t) (digit_char - 'a') < 6) { |
|
digit = 10 + (digit_char - 'a'); |
|
} else { |
|
cpuinfo_log_warning("CPU implementer %.*s in /proc/cpuinfo is ignored due to unexpected non-hex character '%c' at offset %zu", |
|
(int) cpu_implementer_length, cpu_implementer_start, digit_char, (size_t) (digit_ptr - cpu_implementer_start)); |
|
return; |
|
} |
|
cpu_implementer = cpu_implementer * 16 + digit; |
|
} |
|
|
|
processor->midr = midr_set_implementer(processor->midr, cpu_implementer); |
|
processor->flags |= CPUINFO_ARM_LINUX_VALID_IMPLEMENTER | CPUINFO_ARM_LINUX_VALID_PROCESSOR; |
|
} |
|
|
|
static void parse_cpu_variant( |
|
const char* cpu_variant_start, |
|
const char* cpu_variant_end, |
|
struct cpuinfo_arm_linux_processor processor[restrict static 1]) |
|
{ |
|
const size_t cpu_variant_length = cpu_variant_end - cpu_variant_start; |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (cpu_variant_length != 3) { |
|
cpuinfo_log_warning("CPU variant %.*s in /proc/cpuinfo is ignored due to unexpected length (%zu)", |
|
(int) cpu_variant_length, cpu_variant_start, cpu_variant_length); |
|
return; |
|
} |
|
|
|
|
|
if (cpu_variant_start[0] != '0' || cpu_variant_start[1] != 'x') { |
|
cpuinfo_log_warning("CPU variant %.*s in /proc/cpuinfo is ignored due to lack of 0x prefix", |
|
(int) cpu_variant_length, cpu_variant_start); |
|
return; |
|
} |
|
|
|
|
|
const char digit_char = cpu_variant_start[2]; |
|
uint32_t cpu_variant; |
|
if ((uint32_t) (digit_char - '0') < 10) { |
|
cpu_variant = (uint32_t) (digit_char - '0'); |
|
} else if ((uint32_t) (digit_char - 'A') < 6) { |
|
cpu_variant = 10 + (uint32_t) (digit_char - 'A'); |
|
} else if ((uint32_t) (digit_char - 'a') < 6) { |
|
cpu_variant = 10 + (uint32_t) (digit_char - 'a'); |
|
} else { |
|
cpuinfo_log_warning("CPU variant %.*s in /proc/cpuinfo is ignored due to unexpected non-hex character '%c'", |
|
(int) cpu_variant_length, cpu_variant_start, digit_char); |
|
return; |
|
} |
|
|
|
processor->midr = midr_set_variant(processor->midr, cpu_variant); |
|
processor->flags |= CPUINFO_ARM_LINUX_VALID_VARIANT | CPUINFO_ARM_LINUX_VALID_PROCESSOR; |
|
} |
|
|
|
static void parse_cpu_revision( |
|
const char* cpu_revision_start, |
|
const char* cpu_revision_end, |
|
struct cpuinfo_arm_linux_processor processor[restrict static 1]) |
|
{ |
|
uint32_t cpu_revision = 0; |
|
for (const char* digit_ptr = cpu_revision_start; digit_ptr != cpu_revision_end; digit_ptr++) { |
|
const uint32_t digit = (uint32_t) (*digit_ptr - '0'); |
|
|
|
|
|
if (digit >= 10) { |
|
cpuinfo_log_warning("CPU revision %.*s in /proc/cpuinfo is ignored due to unexpected non-digit character '%c' at offset %zu", |
|
(int) (cpu_revision_end - cpu_revision_start), cpu_revision_start, |
|
*digit_ptr, (size_t) (digit_ptr - cpu_revision_start)); |
|
return; |
|
} |
|
|
|
cpu_revision = cpu_revision * 10 + digit; |
|
} |
|
|
|
processor->midr = midr_set_revision(processor->midr, cpu_revision); |
|
processor->flags |= CPUINFO_ARM_LINUX_VALID_REVISION | CPUINFO_ARM_LINUX_VALID_PROCESSOR; |
|
} |
|
|
|
#if CPUINFO_ARCH_ARM |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void parse_cache_number( |
|
const char* number_start, |
|
const char* number_end, |
|
const char* number_name, |
|
uint32_t number_ptr[restrict static 1], |
|
uint32_t flags[restrict static 1], |
|
uint32_t number_mask) |
|
{ |
|
uint32_t number = 0; |
|
for (const char* digit_ptr = number_start; digit_ptr != number_end; digit_ptr++) { |
|
const uint32_t digit = *digit_ptr - '0'; |
|
if (digit >= 10) { |
|
cpuinfo_log_warning("%s %.*s in /proc/cpuinfo is ignored due to unexpected non-digit character '%c' at offset %zu", |
|
number_name, (int) (number_end - number_start), number_start, |
|
*digit_ptr, (size_t) (digit_ptr - number_start)); |
|
return; |
|
} |
|
|
|
number = number * 10 + digit; |
|
} |
|
|
|
if (number == 0) { |
|
cpuinfo_log_warning("%s %.*s in /proc/cpuinfo is ignored due to invalid value of zero reported by the kernel", |
|
number_name, (int) (number_end - number_start), number_start); |
|
} |
|
|
|
|
|
if (number_mask & CPUINFO_ARM_LINUX_VALID_CACHE_LINE) { |
|
switch (number) { |
|
case 16: |
|
case 32: |
|
case 64: |
|
case 128: |
|
break; |
|
default: |
|
cpuinfo_log_warning("invalid %s %.*s is ignored: a value of 16, 32, 64, or 128 expected", |
|
number_name, (int) (number_end - number_start), number_start); |
|
} |
|
} |
|
|
|
*number_ptr = number; |
|
*flags |= number_mask | CPUINFO_ARM_LINUX_VALID_PROCESSOR; |
|
} |
|
#endif |
|
|
|
struct proc_cpuinfo_parser_state { |
|
char* hardware; |
|
char* revision; |
|
uint32_t processor_index; |
|
uint32_t max_processors_count; |
|
struct cpuinfo_arm_linux_processor* processors; |
|
struct cpuinfo_arm_linux_processor dummy_processor; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool parse_line( |
|
const char* line_start, |
|
const char* line_end, |
|
struct proc_cpuinfo_parser_state state[restrict static 1], |
|
uint64_t line_number) |
|
{ |
|
|
|
if (line_start == line_end) { |
|
return true; |
|
} |
|
|
|
|
|
const char* separator = line_start; |
|
for (; separator != line_end; separator++) { |
|
if (*separator == ':') { |
|
break; |
|
} |
|
} |
|
|
|
if (separator == line_end) { |
|
cpuinfo_log_info("Line %.*s in /proc/cpuinfo is ignored: key/value separator ':' not found", |
|
(int) (line_end - line_start), line_start); |
|
return true; |
|
} |
|
|
|
|
|
const char* key_end = separator; |
|
for (; key_end != line_start; key_end--) { |
|
if (key_end[-1] != ' ' && key_end[-1] != '\t') { |
|
break; |
|
} |
|
} |
|
|
|
if (key_end == line_start) { |
|
cpuinfo_log_info("Line %.*s in /proc/cpuinfo is ignored: key contains only spaces", |
|
(int) (line_end - line_start), line_start); |
|
return true; |
|
} |
|
|
|
|
|
const char* value_start = separator + 1; |
|
for (; value_start != line_end; value_start++) { |
|
if (*value_start != ' ') { |
|
break; |
|
} |
|
} |
|
|
|
if (value_start == line_end) { |
|
cpuinfo_log_info("Line %.*s in /proc/cpuinfo is ignored: value contains only spaces", |
|
(int) (line_end - line_start), line_start); |
|
return true; |
|
} |
|
|
|
|
|
const char* value_end = line_end; |
|
for (; value_end != value_start; value_end--) { |
|
if (value_end[-1] != ' ') { |
|
break; |
|
} |
|
} |
|
|
|
const uint32_t processor_index = state->processor_index; |
|
const uint32_t max_processors_count = state->max_processors_count; |
|
struct cpuinfo_arm_linux_processor* processors = state->processors; |
|
struct cpuinfo_arm_linux_processor* processor = &state->dummy_processor; |
|
if (processor_index < max_processors_count) { |
|
processor = &processors[processor_index]; |
|
} |
|
|
|
const size_t key_length = key_end - line_start; |
|
switch (key_length) { |
|
case 6: |
|
if (memcmp(line_start, "Serial", key_length) == 0) { |
|
|
|
#if CPUINFO_ARCH_ARM |
|
} else if (memcmp(line_start, "I size", key_length) == 0) { |
|
parse_cache_number(value_start, value_end, |
|
"instruction cache size", &processor->proc_cpuinfo_cache.i_size, |
|
&processor->flags, CPUINFO_ARM_LINUX_VALID_ICACHE_SIZE); |
|
} else if (memcmp(line_start, "I sets", key_length) == 0) { |
|
parse_cache_number(value_start, value_end, |
|
"instruction cache sets", &processor->proc_cpuinfo_cache.i_sets, |
|
&processor->flags, CPUINFO_ARM_LINUX_VALID_ICACHE_SETS); |
|
} else if (memcmp(line_start, "D size", key_length) == 0) { |
|
parse_cache_number(value_start, value_end, |
|
"data cache size", &processor->proc_cpuinfo_cache.d_size, |
|
&processor->flags, CPUINFO_ARM_LINUX_VALID_DCACHE_SIZE); |
|
} else if (memcmp(line_start, "D sets", key_length) == 0) { |
|
parse_cache_number(value_start, value_end, |
|
"data cache sets", &processor->proc_cpuinfo_cache.d_sets, |
|
&processor->flags, CPUINFO_ARM_LINUX_VALID_DCACHE_SETS); |
|
#endif |
|
} else { |
|
goto unknown; |
|
} |
|
break; |
|
#if CPUINFO_ARCH_ARM |
|
case 7: |
|
if (memcmp(line_start, "I assoc", key_length) == 0) { |
|
parse_cache_number(value_start, value_end, |
|
"instruction cache associativity", &processor->proc_cpuinfo_cache.i_assoc, |
|
&processor->flags, CPUINFO_ARM_LINUX_VALID_ICACHE_WAYS); |
|
} else if (memcmp(line_start, "D assoc", key_length) == 0) { |
|
parse_cache_number(value_start, value_end, |
|
"data cache associativity", &processor->proc_cpuinfo_cache.d_assoc, |
|
&processor->flags, CPUINFO_ARM_LINUX_VALID_DCACHE_WAYS); |
|
} else { |
|
goto unknown; |
|
} |
|
break; |
|
#endif |
|
case 8: |
|
if (memcmp(line_start, "CPU part", key_length) == 0) { |
|
parse_cpu_part(value_start, value_end, processor); |
|
} else if (memcmp(line_start, "Features", key_length) == 0) { |
|
parse_features(value_start, value_end, processor); |
|
} else if (memcmp(line_start, "BogoMIPS", key_length) == 0) { |
|
|
|
} else if (memcmp(line_start, "Hardware", key_length) == 0) { |
|
size_t value_length = value_end - value_start; |
|
if (value_length > CPUINFO_HARDWARE_VALUE_MAX) { |
|
cpuinfo_log_info( |
|
"length of Hardware value \"%.*s\" in /proc/cpuinfo exceeds limit (%d): truncating to the limit", |
|
(int) value_length, value_start, CPUINFO_HARDWARE_VALUE_MAX); |
|
value_length = CPUINFO_HARDWARE_VALUE_MAX; |
|
} else { |
|
state->hardware[value_length] = '\0'; |
|
} |
|
memcpy(state->hardware, value_start, value_length); |
|
cpuinfo_log_debug("parsed /proc/cpuinfo Hardware = \"%.*s\"", (int) value_length, value_start); |
|
} else if (memcmp(line_start, "Revision", key_length) == 0) { |
|
size_t value_length = value_end - value_start; |
|
if (value_length > CPUINFO_REVISION_VALUE_MAX) { |
|
cpuinfo_log_info( |
|
"length of Revision value \"%.*s\" in /proc/cpuinfo exceeds limit (%d): truncating to the limit", |
|
(int) value_length, value_start, CPUINFO_REVISION_VALUE_MAX); |
|
value_length = CPUINFO_REVISION_VALUE_MAX; |
|
} else { |
|
state->revision[value_length] = '\0'; |
|
} |
|
memcpy(state->revision, value_start, value_length); |
|
cpuinfo_log_debug("parsed /proc/cpuinfo Revision = \"%.*s\"", (int) value_length, value_start); |
|
} else { |
|
goto unknown; |
|
} |
|
break; |
|
case 9: |
|
if (memcmp(line_start, "processor", key_length) == 0) { |
|
const uint32_t new_processor_index = parse_processor_number(value_start, value_end); |
|
if (new_processor_index < processor_index) { |
|
|
|
cpuinfo_log_warning( |
|
"unexpectedly low processor number %"PRIu32" following processor %"PRIu32" in /proc/cpuinfo", |
|
new_processor_index, processor_index); |
|
} else if (new_processor_index > processor_index + 1) { |
|
|
|
cpuinfo_log_info( |
|
"unexpectedly high processor number %"PRIu32" following processor %"PRIu32" in /proc/cpuinfo", |
|
new_processor_index, processor_index); |
|
} |
|
if (new_processor_index < max_processors_count) { |
|
|
|
processors[new_processor_index].flags |= CPUINFO_ARM_LINUX_VALID_PROCESSOR; |
|
} else { |
|
|
|
cpuinfo_log_warning("processor %"PRIu32" in /proc/cpuinfo is ignored: index exceeds system limit %"PRIu32, |
|
new_processor_index, max_processors_count - 1); |
|
} |
|
state->processor_index = new_processor_index; |
|
return true; |
|
} else if (memcmp(line_start, "Processor", key_length) == 0) { |
|
|
|
} else { |
|
goto unknown; |
|
} |
|
break; |
|
case 11: |
|
if (memcmp(line_start, "CPU variant", key_length) == 0) { |
|
parse_cpu_variant(value_start, value_end, processor); |
|
} else { |
|
goto unknown; |
|
} |
|
break; |
|
case 12: |
|
if (memcmp(line_start, "CPU revision", key_length) == 0) { |
|
parse_cpu_revision(value_start, value_end, processor); |
|
} else { |
|
goto unknown; |
|
} |
|
break; |
|
#if CPUINFO_ARCH_ARM |
|
case 13: |
|
if (memcmp(line_start, "I line length", key_length) == 0) { |
|
parse_cache_number(value_start, value_end, |
|
"instruction cache line size", &processor->proc_cpuinfo_cache.i_line_length, |
|
&processor->flags, CPUINFO_ARM_LINUX_VALID_ICACHE_LINE); |
|
} else if (memcmp(line_start, "D line length", key_length) == 0) { |
|
parse_cache_number(value_start, value_end, |
|
"data cache line size", &processor->proc_cpuinfo_cache.d_line_length, |
|
&processor->flags, CPUINFO_ARM_LINUX_VALID_DCACHE_LINE); |
|
} else { |
|
goto unknown; |
|
} |
|
break; |
|
#endif |
|
case 15: |
|
if (memcmp(line_start, "CPU implementer", key_length) == 0) { |
|
parse_cpu_implementer(value_start, value_end, processor); |
|
} else if (memcmp(line_start, "CPU implementor", key_length) == 0) { |
|
parse_cpu_implementer(value_start, value_end, processor); |
|
} else { |
|
goto unknown; |
|
} |
|
break; |
|
case 16: |
|
if (memcmp(line_start, "CPU architecture", key_length) == 0) { |
|
parse_cpu_architecture(value_start, value_end, processor); |
|
} else { |
|
goto unknown; |
|
} |
|
break; |
|
default: |
|
unknown: |
|
cpuinfo_log_debug("unknown /proc/cpuinfo key: %.*s", (int) key_length, line_start); |
|
|
|
} |
|
return true; |
|
} |
|
|
|
bool cpuinfo_arm_linux_parse_proc_cpuinfo( |
|
char hardware[restrict static CPUINFO_HARDWARE_VALUE_MAX], |
|
char revision[restrict static CPUINFO_REVISION_VALUE_MAX], |
|
uint32_t max_processors_count, |
|
struct cpuinfo_arm_linux_processor processors[restrict static max_processors_count]) |
|
{ |
|
hardware[0] = '\0'; |
|
struct proc_cpuinfo_parser_state state = { |
|
.hardware = hardware, |
|
.revision = revision, |
|
.processor_index = 0, |
|
.max_processors_count = max_processors_count, |
|
.processors = processors, |
|
}; |
|
return cpuinfo_linux_parse_multiline_file("/proc/cpuinfo", BUFFER_SIZE, |
|
(cpuinfo_line_callback) parse_line, &state); |
|
} |
|
|