diff --git a/CMakeLists.txt b/CMakeLists.txt index bd9f77f8..16e869b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,7 @@ IF(NOT CMAKE_SYSTEM_PROCESSOR) "cpuinfo will compile, but cpuinfo_initialize() will always fail.") SET(CPUINFO_SUPPORTED_PLATFORM FALSE) ENDIF() -ELSEIF(NOT CPUINFO_TARGET_PROCESSOR MATCHES "^(i[3-6]86|AMD64|x86(_64)?|armv[5-8].*|aarch64|arm64.*|ARM64.*|riscv(32|64))$") +ELSEIF(NOT CPUINFO_TARGET_PROCESSOR MATCHES "^(i[3-6]86|AMD64|x86(_64)?|armv[5-8].*|aarch64|arm64.*|ARM64.*|riscv(32|64)|loongarch64)$") MESSAGE(WARNING "Target processor architecture \"${CPUINFO_TARGET_PROCESSOR}\" is not supported in cpuinfo. " "cpuinfo will compile, but cpuinfo_initialize() will always fail.") @@ -224,6 +224,16 @@ IF(CPUINFO_SUPPORTED_PLATFORM) src/riscv/linux/riscv-hw.c src/riscv/linux/riscv-isa.c) ENDIF() + ELSEIF(CPUINFO_TARGET_PROCESSOR MATCHES "^(loongarch64)$") + LIST(APPEND CPUINFO_SRCS + src/loongarch/uarch.c) + IF(CMAKE_SYSTEM_NAME STREQUAL "Linux") + LIST(APPEND CPUINFO_SRCS + src/loongarch/linux/init.c + src/loongarch/linux/cpuinfo.c + src/loongarch/linux/cache.c + src/loongarch/linux/loongarch64-isa.c) + ENDIF() ENDIF() IF(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") diff --git a/configure.py b/configure.py index 00bba24b..f8a4b3d1 100755 --- a/configure.py +++ b/configure.py @@ -63,6 +63,16 @@ def main(args): "riscv/linux/riscv-isa.c", ] + if build.target.is_loongarch64: + sources += ["loongarch/uarch.c"] + if build.target.is_linux: + sources += [ + "loongarch/linux/init.c", + "loongarch/linux/cpuinfo.c", + "loongarch/linux/cache.c", + "loongarch/linux/loongarch64-isa.c", + ] + if build.target.is_macos: sources += ["mach/topology.c"] if build.target.is_linux or build.target.is_android: diff --git a/include/cpuinfo.h b/include/cpuinfo.h index 6eb4b8c3..049bc321 100644 --- a/include/cpuinfo.h +++ b/include/cpuinfo.h @@ -54,6 +54,10 @@ #endif #endif +#if defined(__loongarch64) +#define CPUINFO_ARCH_LOONGARCH64 1 +#endif + /* Define other architecture-specific macros as 0 */ #ifndef CPUINFO_ARCH_X86 @@ -96,6 +100,10 @@ #define CPUINFO_ARCH_RISCV64 0 #endif +#ifndef CPUINFO_ARCH_LOONGARCH64 +#define CPUINFO_ARCH_LOONGARCH64 0 +#endif + #if CPUINFO_ARCH_X86 && defined(_MSC_VER) #define CPUINFO_ABI __cdecl #elif CPUINFO_ARCH_X86 && defined(__GNUC__) @@ -304,6 +312,10 @@ enum cpuinfo_vendor { * in 1997. */ cpuinfo_vendor_dec = 57, + /** + * Loongson. Vendor of LOONGARCH processor microarchitecture. + */ + cpuinfo_vendor_loongson = 58, }; /** @@ -601,6 +613,15 @@ enum cpuinfo_uarch { /** HiSilicon TaiShan v110 (Huawei Kunpeng 920 series processors). */ cpuinfo_uarch_taishan_v110 = 0x00C00100, + + /** Loongson 64bit, 2-issue. */ + cpuinfo_uarch_LA264 = 0x00D00100, + /** Loongson 64bit, 3-issue. */ + cpuinfo_uarch_LA364 = 0x00D00101, + /** Loongson 64bit, 4-issue. */ + cpuinfo_uarch_LA464 = 0x00D00102, + /** Loongson 64bit, 6-issue. */ + cpuinfo_uarch_LA664 = 0x00D00103, }; struct cpuinfo_processor { @@ -671,6 +692,9 @@ struct cpuinfo_core { #elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 /** Value of Main ID Register (MIDR) for this core */ uint32_t midr; +#elif CPUINFO_ARCH_LOONGARCH64 + /** Value of PRocessorID (PRID) for this core */ + uint32_t prid; #endif /** Clock rate (non-Turbo) of the core, in Hz */ uint64_t frequency; @@ -699,6 +723,9 @@ struct cpuinfo_cluster { #elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 /** Value of Main ID Register (MIDR) of the cores in the cluster */ uint32_t midr; +#elif CPUINFO_ARCH_LOONGARCH64 + /** Value of PRID for this cores in the cluster */ + uint32_t prid; #endif /** Clock rate (non-Turbo) of the cores in the cluster, in Hz */ uint64_t frequency; @@ -732,6 +759,9 @@ struct cpuinfo_uarch_info { #elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 /** Value of Main ID Register (MIDR) for the microarchitecture */ uint32_t midr; +#elif CPUINFO_ARCH_LOONGARCH64 + /** Value of PRID for the microarchitecture */ + uint32_t prid; #endif /** Number of logical processors with the microarchitecture */ uint32_t processor_count; @@ -2227,6 +2257,150 @@ static inline bool cpuinfo_has_riscv_v(void) { #endif } +#if CPUINFO_ARCH_LOONGARCH64 +/* This structure is not a part of stable API. Use cpuinfo_has_loongarch_* functions instead. */ +struct cpuinfo_loongarch_isa { + bool cpucfg; + bool lam; + bool ual; + bool fpu; + bool lsx; + bool lasx; + + bool crc32; + bool complex; + bool crypto; + bool lvz; + bool lbt_x86; + bool lbt_arm; + bool lbt_mips; + bool ptw; + bool lspw; +}; + +extern struct cpuinfo_loongarch_isa cpuinfo_isa; +#endif + +static inline bool cpuinfo_has_loongarch_cpucfg(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.cpucfg; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_lam(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.lam; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_ual(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.ual; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_fpu(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.fpu; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_lsx(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.lsx; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_lasx(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.lasx; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_crc32(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.crc32; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_complex(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.complex; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_crypto(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.crypto; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_lvz(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.lvz; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_lbt_x86(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.lbt_x86; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_lbt_arm(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.lbt_arm; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_lbt_mips(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.lbt_mips; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_ptw(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.ptw; +#else + return false; +#endif +} + +static inline bool cpuinfo_has_loongarch_lspw(void) { +#if CPUINFO_ARCH_LOONGARCH64 + return cpuinfo_isa.lspw; +#else + return false; +#endif +} + const struct cpuinfo_processor* CPUINFO_ABI cpuinfo_get_processors(void); const struct cpuinfo_core* CPUINFO_ABI cpuinfo_get_cores(void); const struct cpuinfo_cluster* CPUINFO_ABI cpuinfo_get_clusters(void); diff --git a/src/api.c b/src/api.c index b8c999f3..ec60d9a1 100644 --- a/src/api.c +++ b/src/api.c @@ -30,7 +30,7 @@ uint32_t cpuinfo_packages_count = 0; uint32_t cpuinfo_cache_count[cpuinfo_cache_level_max] = {0}; uint32_t cpuinfo_max_cache_size = 0; -#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 +#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 || CPUINFO_ARCH_LOONGARCH64 struct cpuinfo_uarch_info* cpuinfo_uarchs = NULL; uint32_t cpuinfo_uarchs_count = 0; #else @@ -41,7 +41,7 @@ struct cpuinfo_uarch_info cpuinfo_global_uarch = {cpuinfo_uarch_unknown}; uint32_t cpuinfo_linux_cpu_max = 0; const struct cpuinfo_processor** cpuinfo_linux_cpu_to_processor_map = NULL; const struct cpuinfo_core** cpuinfo_linux_cpu_to_core_map = NULL; -#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 +#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 || CPUINFO_ARCH_LOONGARCH64 const uint32_t* cpuinfo_linux_cpu_to_uarch_index_map = NULL; #endif #endif @@ -78,7 +78,7 @@ const struct cpuinfo_uarch_info* cpuinfo_get_uarchs() { if (!cpuinfo_is_initialized) { cpuinfo_log_fatal("cpuinfo_get_%s called before cpuinfo is initialized", "uarchs"); } -#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 +#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 || CPUINFO_ARCH_LOONGARCH64 return cpuinfo_uarchs; #else return &cpuinfo_global_uarch; @@ -129,7 +129,7 @@ const struct cpuinfo_uarch_info* cpuinfo_get_uarch(uint32_t index) { if (!cpuinfo_is_initialized) { cpuinfo_log_fatal("cpuinfo_get_%s called before cpuinfo is initialized", "uarch"); } -#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 +#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 || CPUINFO_ARCH_LOONGARCH64 if CPUINFO_UNLIKELY (index >= cpuinfo_uarchs_count) { return NULL; } @@ -174,7 +174,7 @@ uint32_t cpuinfo_get_uarchs_count(void) { if (!cpuinfo_is_initialized) { cpuinfo_log_fatal("cpuinfo_get_%s called before cpuinfo is initialized", "uarchs_count"); } -#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 +#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 || CPUINFO_ARCH_LOONGARCH64 return cpuinfo_uarchs_count; #else return 1; diff --git a/src/cpuinfo/internal-api.h b/src/cpuinfo/internal-api.h index d84b26a8..3273cd8b 100644 --- a/src/cpuinfo/internal-api.h +++ b/src/cpuinfo/internal-api.h @@ -34,7 +34,7 @@ extern CPUINFO_INTERNAL uint32_t cpuinfo_packages_count; extern CPUINFO_INTERNAL uint32_t cpuinfo_cache_count[cpuinfo_cache_level_max]; extern CPUINFO_INTERNAL uint32_t cpuinfo_max_cache_size; -#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 +#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 || CPUINFO_ARCH_LOONGARCH64 extern CPUINFO_INTERNAL struct cpuinfo_uarch_info* cpuinfo_uarchs; extern CPUINFO_INTERNAL uint32_t cpuinfo_uarchs_count; #else @@ -61,6 +61,7 @@ CPUINFO_PRIVATE void cpuinfo_arm_mach_init(void); CPUINFO_PRIVATE void cpuinfo_arm_linux_init(void); CPUINFO_PRIVATE void cpuinfo_riscv_linux_init(void); CPUINFO_PRIVATE void cpuinfo_emscripten_init(void); +CPUINFO_PRIVATE void cpuinfo_loongarch_linux_init(void); CPUINFO_PRIVATE uint32_t cpuinfo_compute_max_cache_size(const struct cpuinfo_processor* processor); diff --git a/src/init.c b/src/init.c index 81d5721c..c9e068ba 100644 --- a/src/init.c +++ b/src/init.c @@ -58,6 +58,12 @@ bool CPUINFO_ABI cpuinfo_initialize(void) { } init_guard = true; #endif +#elif CPUINFO_ARCH_LOONGARCH64 +#if defined(__linux__) + pthread_once(&init_guard, &cpuinfo_loongarch_linux_init); +#else + cpuinfo_log_error("loongarch operating system is not supported in cpuinfo"); +#endif #else cpuinfo_log_error("processor architecture is not supported in cpuinfo"); #endif diff --git a/src/linux/processors.c b/src/linux/processors.c index b68cd1cc..f33fff97 100644 --- a/src/linux/processors.c +++ b/src/linux/processors.c @@ -293,7 +293,7 @@ uint32_t cpuinfo_linux_get_max_possible_processor(uint32_t max_processors_count) uint32_t max_possible_processor = 0; if (!cpuinfo_linux_parse_cpulist( POSSIBLE_CPULIST_FILENAME, max_processor_number_parser, &max_possible_processor)) { -#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 +#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_LOONGARCH64 cpuinfo_log_error("failed to parse the list of possible processors in %s", POSSIBLE_CPULIST_FILENAME); #else cpuinfo_log_warning("failed to parse the list of possible processors in %s", POSSIBLE_CPULIST_FILENAME); @@ -315,7 +315,7 @@ uint32_t cpuinfo_linux_get_max_present_processor(uint32_t max_processors_count) uint32_t max_present_processor = 0; if (!cpuinfo_linux_parse_cpulist( PRESENT_CPULIST_FILENAME, max_processor_number_parser, &max_present_processor)) { -#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 +#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_LOONGARCH64 cpuinfo_log_error("failed to parse the list of present processors in %s", PRESENT_CPULIST_FILENAME); #else cpuinfo_log_warning("failed to parse the list of present processors in %s", PRESENT_CPULIST_FILENAME); diff --git a/src/loongarch/api.h b/src/loongarch/api.h new file mode 100644 index 00000000..11610e93 --- /dev/null +++ b/src/loongarch/api.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#include +#include + +CPUINFO_INTERNAL void cpuinfo_loongarch_decode_vendor_uarch( + uint32_t prid, + enum cpuinfo_vendor vendor[restrict static 1], + enum cpuinfo_uarch uarch[restrict static 1]); diff --git a/src/loongarch/cpucfg.h b/src/loongarch/cpucfg.h new file mode 100644 index 00000000..2004fe3f --- /dev/null +++ b/src/loongarch/cpucfg.h @@ -0,0 +1,51 @@ +#pragma once +#include + +#if defined(__GNUC__) +#include +#endif + +enum cpucfg_regs { + CPUCFG_REG_PRID = 0, + CPUCFG_REG_CACHE_BASEINFO = 0x10, + CPUCFG_REG_CACHE_L1_IU, + CPUCFG_REG_CACHE_L1_D, + CPUCFG_REG_CACHE_L2_IU, + CPUCFG_REG_CACHE_L3_IU, +}; + +static inline bool cpucfg(uint32_t reg, uint32_t *value) { +#if defined(__GNUC__) + *value = __cpucfg(reg); + return true; +#else + return false; +#endif +} + +enum cache_baseinfo_bit { + L1_IU_Present = 0, + L1_IU_Unify, + L1_D_Present, + L2_IU_Present, + L2_IU_Unify, + L2_IU_Private, + L2_IU_Inclusive, + L2_D_Present, + L2_D_Private, + L2_D_Inclusive, + L3_IU_Present, + L3_IU_Unify, + L3_IU_Private, + L3_IU_Inclusive, + L3_D_Present, + L3_D_Private, + L3_D_Inclusive, +}; + +#define CACHE_WAYS_OFFSET 0 +#define CACHE_SETS_OFFSET 16 +#define CACHE_LSIZE_OFFSET 24 +#define CACHE_WAYS_MASK UINT32_C(0x0000FFFF) +#define CACHE_SETS_MASK UINT32_C(0x00FF0000) +#define CACHE_LSIZE_MASK UINT32_C(0x7F000000) diff --git a/src/loongarch/linux/api.h b/src/loongarch/linux/api.h new file mode 100644 index 00000000..f919789e --- /dev/null +++ b/src/loongarch/linux/api.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +/* No hard limit in the kernel, maximum length observed on non-rogue kernels is 64 */ +#define CPUINFO_HARDWARE_VALUE_MAX 64 + +#if CPUINFO_ARCH_LOONGARCH64 +/* Linux: arch/loongarch/include/uapi/asm/hwcap.h */ +#define CPUINFO_LOONGARCH_LINUX_FEATURE_CPUCFG UINT32_C(0x00000001) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_LAM UINT32_C(0x00000002) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_UAL UINT32_C(0x00000004) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_FPU UINT32_C(0x00000008) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_LSX UINT32_C(0x00000010) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_LASX UINT32_C(0x00000020) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_CRC32 UINT32_C(0x00000040) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_COMPLEX UINT32_C(0x00000080) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_CRYPTO UINT32_C(0x00000100) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_LVZ UINT32_C(0x00000200) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_LBT_X86 UINT32_C(0x00000400) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_LBT_ARM UINT32_C(0x00000800) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_LBT_MIPS UINT32_C(0x00001000) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_PTW UINT32_C(0x00002000) +#define CPUINFO_LOONGARCH_LINUX_FEATURE_LSPW UINT32_C(0x00004000) +#endif + +#define CPUINFO_LOONGARCH_LINUX_VALID_COMPANYID UINT32_C(0x00010000) +#define CPUINFO_LOONGARCH_LINUX_VALID_SERIESID UINT32_C(0x00020000) +#define CPUINFO_LOONGARCH_LINUX_VALID_REVISION UINT32_C(0x00040000) +#define CPUINFO_LOONGARCH_LINUX_VALID_PROCESSOR UINT32_C(0x00200000) +#define CPUINFO_LOONGARCH_LINUX_VALID_FEATURES UINT32_C(0x00400000) +#define CPUINFO_LOONGARCH_LINUX_VALID_INFO UINT32_C(0x007F0000) +#define CPUINFO_LOONGARCH_LINUX_VALID_L1I UINT32_C(0x01000000) +#define CPUINFO_LOONGARCH_LINUX_VALID_L1D UINT32_C(0x02000000) +#define CPUINFO_LOONGARCH_LINUX_VALID_L2 UINT32_C(0x04000000) +#define CPUINFO_LOONGARCH_LINUX_VALID_L3 UINT32_C(0x08000000) +#define CPUINFO_LOONGARCH_LINUX_VALID_PRID (CPUINFO_LOONGARCH_LINUX_VALID_COMPANYID | CPUINFO_LOONGARCH_LINUX_VALID_SERIESID | CPUINFO_LOONGARCH_LINUX_VALID_REVISION) +#define CPUINFO_LOONGARCH_LINUX_VALID_CACHE (CPUINFO_LOONGARCH_LINUX_VALID_L1I | CPUINFO_LOONGARCH_LINUX_VALID_L1D | CPUINFO_LOONGARCH_LINUX_VALID_L2 | CPUINFO_LOONGARCH_LINUX_VALID_L3) + +struct cpuinfo_loongarch_linux_processor { + uint32_t features; + uint32_t prid; + enum cpuinfo_vendor vendor; + enum cpuinfo_uarch uarch; + uint32_t uarch_index; + /** + * ID of the physical package which includes this logical processor. + * The value is parsed from /sys/devices/system/cpu/cpu/topology/physical_package_id + */ + uint32_t package_id; + /** + * Minimum processor ID on the package which includes this logical processor. + * This value can serve as an ID for the cluster of logical processors: it is the + * same for all logical processors on the same package. + */ + uint32_t package_leader_id; + /** + * Number of logical processors in the package. + */ + uint32_t package_processor_count; + + /** Linux processor ID */ + uint32_t system_processor_id; + /** CoreID */ + uint32_t core_id; + /** SmtID */ + uint32_t smt_id; + + /** Cache info */ + struct cpuinfo_cache l1i; + struct cpuinfo_cache l1d; + struct cpuinfo_cache l2; + struct cpuinfo_cache l3; + + /** Hardware name */ + char hardware_name[CPUINFO_HARDWARE_VALUE_MAX]; + + uint32_t flags; +}; + + +CPUINFO_INTERNAL bool cpuinfo_loongarch_linux_parse_proc_cpuinfo( + uint32_t max_processors_count, + struct cpuinfo_loongarch_linux_processor processors[restrict static max_processors_count]); + +#if CPUINFO_ARCH_LOONGARCH64 + CPUINFO_INTERNAL void cpuinfo_loongarch64_linux_decode_isa_from_hwcap( + struct cpuinfo_loongarch_isa isa[restrict static 1]); +#endif + +CPUINFO_INTERNAL bool cpuinfo_loongarch_linux_parse_cpu_cache( + uint32_t max_processors_count, + struct cpuinfo_loongarch_linux_processor processors[restrict static max_processors_count]); + +extern CPUINFO_INTERNAL const uint32_t* cpuinfo_linux_cpu_to_uarch_index_map; +extern CPUINFO_INTERNAL uint32_t cpuinfo_linux_cpu_to_uarch_index_map_entries; diff --git a/src/loongarch/linux/cache.c b/src/loongarch/linux/cache.c new file mode 100644 index 00000000..225def9d --- /dev/null +++ b/src/loongarch/linux/cache.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include + +#include +#include +#include + +#define BUFFER_SIZE 64 +#define STRINGIFY(token) #token +#define CACHE_INDEX_SIZE \ + (sizeof("/sys/devices/system/cpu/cpu" STRINGIFY(UINT32_MAX) "/cache/index" STRINGIFY(UINT32_MAX) "/") + 32) +#define CACHE_INDEX_FORMAT "/sys/devices/system/cpu/cpu%" PRIu32 "/cache/index%" PRIu32 "/%s" +#define FILENAME_SETS STRINGIFY(number_of_sets) +#define FILENAME_WAYS STRINGIFY(ways_of_associativity) +#define FILENAME_LSIZE STRINGIFY(coherency_line_size) +#define FILENAME_LEVEL STRINGIFY(level) +#define FILENAME_TYPE STRINGIFY(type) +#define FILENAME_SIZE STRINGIFY(size) + +enum cache_type { + Unknown, + Instruction, + Data, + Unified, +}; + +static bool parse_number( + const char* line_start, + const char* line_end, + uint32_t number_ptr[restrict static 1], + uint64_t line_number) +{ + uint32_t number = 0; + const char* parsed = line_start; + if (line_number != 1) { + return true; + } + for (; parsed != line_end && *parsed != '\0'; parsed++) { + const uint32_t digit = (uint32_t)(uint8_t)(*parsed) - (uint32_t)'0'; + if (digit >= 10) { + return false; + } + number = number * UINT32_C(10) + digit; + } + *number_ptr = number; + return true; +} + +static bool parse_size( + const char* line_start, + const char* line_end, + uint32_t number_ptr[restrict static 1], + uint64_t line_number) +{ + uint32_t number = 0; + const char* parsed = line_start; + if (line_number != 1) { + return true; + } + for (; parsed != line_end && *parsed != 'K'; parsed++) { + const uint32_t digit = (uint32_t)(uint8_t)(*parsed) - (uint32_t)'0'; + if (digit >= 10) { + return false; + } + number = number * UINT32_C(10) + digit; + } + if (*parsed != 'K') { + return false; + } + *number_ptr = number; + return true; +} + +static inline size_t min(size_t a, size_t b) { + return a < b ? a : b; +} + +static bool parse_type( + const char* line_start, + const char* line_end, + enum cache_type type_ptr[restrict static 1], + uint64_t line_number) +{ + size_t line_length = line_end - line_start; + + if (line_number != 1) { + return true; + } + if (0 == strncmp("Instruction", line_start, min(line_length, sizeof("Instruction")))) { + *type_ptr = Instruction; + return true; + } + if (0 == strncmp("Data", line_start, min(line_length, sizeof("Data")))) { + *type_ptr = Data; + return true; + } + if (0 == strncmp("Unified", line_start, min(line_length, sizeof("Unified")))) { + *type_ptr = Unified; + return true; + } + return false; +} + +static bool parse( + const char* suffix, uint32_t x, uint32_t y, void *data_ptr, + cpuinfo_line_callback parse_func) +{ + char filename[CACHE_INDEX_SIZE]; + const int chars_formatted = + snprintf(filename, CACHE_INDEX_SIZE, CACHE_INDEX_FORMAT, x, y, suffix); + if ((unsigned int)chars_formatted >= CACHE_INDEX_SIZE) { + cpuinfo_log_warning("failed to format filename for cache index %s", suffix); + return true; + } + if (!cpuinfo_linux_parse_multiline_file(filename, BUFFER_SIZE, parse_func, data_ptr)) { + cpuinfo_log_error("failed parse cache index %s", suffix); + return false; + } + return true; +} + +static bool cpuinfo_loongarch_linux_parse_cpuX_cache_indexY( + uint32_t x, uint32_t y, + struct cpuinfo_loongarch_linux_processor processors[restrict static 1]) +{ + uint32_t sets, ways, level, line_size, size; + enum cache_type type = Unknown; + char filename[CACHE_INDEX_SIZE]; + struct cpuinfo_cache *cache; + + if (!parse(FILENAME_SETS, x, y, &sets, (cpuinfo_line_callback) parse_number)) { + return false; + } + if (!parse(FILENAME_WAYS, x, y, &ways, (cpuinfo_line_callback) parse_number)) { + return false; + } + if (!parse(FILENAME_LSIZE, x, y, &line_size, (cpuinfo_line_callback) parse_number)) { + return false; + } + if (!parse(FILENAME_LEVEL, x, y, &level, (cpuinfo_line_callback) parse_number)) { + return false; + } + if (!parse(FILENAME_TYPE, x, y, &type, (cpuinfo_line_callback) parse_type)) { + return false; + } + if (!parse(FILENAME_SIZE, x, y, &size, (cpuinfo_line_callback) parse_size)) { + return false; + } + + if (level == 1 && type == Instruction) { + cache = &processors->l1i; + processors->flags |= CPUINFO_LOONGARCH_LINUX_VALID_L1I; + } else if (level == 1 && type == Data) { + cache = &processors->l1d; + processors->flags |= CPUINFO_LOONGARCH_LINUX_VALID_L1D; + } else if (level == 2 && type == Unified) { + cache = &processors->l2; + processors->flags |= CPUINFO_LOONGARCH_LINUX_VALID_L2; + } else if (level == 3 && type == Unified) { + cache = &processors->l3; + processors->flags |= CPUINFO_LOONGARCH_LINUX_VALID_L3; + } else { + return false; + } + + *cache = (struct cpuinfo_cache) { + .associativity = ways, + .sets = sets, + .line_size = line_size, + .size = size * 1024, + .partitions = 1, + }; + return true; +} + +bool cpuinfo_loongarch_linux_parse_cpu_cache( + uint32_t max_processors_count, + struct cpuinfo_loongarch_linux_processor processors[restrict static max_processors_count]) +{ + + for (uint32_t i = 0; i < max_processors_count; i++) { + // TODO dynamic detect + for (uint32_t j = 0; j < 4; j++) { + if ((processors[i].flags & CPUINFO_LOONGARCH_LINUX_VALID_L1D) && j == 0) { + continue; + } + if ((processors[i].flags & CPUINFO_LOONGARCH_LINUX_VALID_L1I) && j == 1) { + continue; + } + if ((processors[i].flags & CPUINFO_LOONGARCH_LINUX_VALID_L2) && j == 2) { + continue; + } + if ((processors[i].flags & CPUINFO_LOONGARCH_LINUX_VALID_L3) && j == 3) { + continue; + } + if (!cpuinfo_loongarch_linux_parse_cpuX_cache_indexY(i, j, &processors[i])) + return false; + } + } + return true; +} diff --git a/src/loongarch/linux/cpuinfo.c b/src/loongarch/linux/cpuinfo.c new file mode 100644 index 00000000..58913def --- /dev/null +++ b/src/loongarch/linux/cpuinfo.c @@ -0,0 +1,616 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * Size, in chars, of the on-stack buffer used for parsing lines of /proc/cpuinfo. + * This is also the limit on the length of a single line. + */ +#define BUFFER_SIZE 1024 + +struct cpuinfo_loongarch_seriesID { + const char* prefix; + enum prid_series seriesID; +}; + +static const struct cpuinfo_loongarch_seriesID loongson_name_map_seriesID[] = { + { .prefix = "3A5000", .seriesID = prid_series_la464, }, + { .prefix = "3C5000", .seriesID = prid_series_la464, }, + { .prefix = "3D5000", .seriesID = prid_series_la464, }, + { .prefix = "3A6000", .seriesID = prid_series_la664, }, + { .prefix = "3C6000", .seriesID = prid_series_la664, }, +}; + +static inline size_t min(size_t a, size_t b) { + return a < b ? a : b; +} + +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; +} + +/* + * Full list of Loongarch features reported in /proc/cpuinfo: + */ +static void parse_features( + const char* features_start, + const char* features_end, + struct cpuinfo_loongarch_linux_processor processor[restrict static 1]) +{ + const char* feature_start = features_start; + const char* feature_end; + + /* Mark the features as valid */ + processor->flags |= CPUINFO_LOONGARCH_LINUX_VALID_FEATURES; + + 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 3: + if (memcmp(feature_start, "lam", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_LAM; + #endif + } else if (memcmp(feature_start, "ual", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_UAL; + #endif + } else if (memcmp(feature_start, "lsx", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_LSX; + #endif + } else if (memcmp(feature_start, "fpu", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_FPU; + #endif + } else if (memcmp(feature_start, "lvz", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_LVZ; + #endif + } else if (memcmp(feature_start, "ptw", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_PTW; + #endif + } else { + goto unexpected; + } + break; + case 4: + if (memcmp(feature_start, "lasx", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_LASX; + #endif + } else if (memcmp(feature_start, "lspw", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_LSPW; + #endif + } else { + goto unexpected; + } + break; + case 5: + if (memcmp(feature_start, "crc32", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_CRC32; + #endif + } else { + goto unexpected; + } + break; + case 6: + if (memcmp(feature_start, "crypto", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_CRYPTO; + #endif + } else if (memcmp(feature_start, "cpucfg", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_CPUCFG; + #endif + } else { + goto unexpected; + } + break; + case 7: + if (memcmp(feature_start, "complex", feature_length) == 0) { + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_COMPLEX; + } else if (memcmp(feature_start, "lbt_x86", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_LBT_X86; + #endif + } else if (memcmp(feature_start, "lbt_arm", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_LBT_ARM; + #endif + } else { + goto unexpected; + } + break; + case 8: + if (memcmp(feature_start, "lbt_mips", feature_length) == 0) { + #if CPUINFO_ARCH_LOONGARCH64 + processor->features |= CPUINFO_LOONGARCH_LINUX_FEATURE_LBT_MIPS; + #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 bool parse_loongson(const char* name_start, size_t length){ + /* expected loongson , its length is eight */ + if(length != 8) return false; + /* expected loongson , its first char is 'l' or 'L' */ + if(name_start[0] != 'l' && name_start[0] != 'L') return false; + + char* elsechars = "oongson"; + for(int i = 0;i<7;i++){ + if(name_start[i+1] != elsechars[i]) return false; + } + return true; +} + +static void parse_seriesID(const char* name_start, size_t length, int* seriesID){ + for (size_t i = 0; i < CPUINFO_COUNT_OF(loongson_name_map_seriesID); i++) { + const struct cpuinfo_loongarch_seriesID *cur = &loongson_name_map_seriesID[i]; + if (strncmp(cur->prefix, name_start, strlen(cur->prefix))) + continue; + cpuinfo_log_debug( + "found /proc/cpuinfo model name second string \"%.*s\" in loongson seriesID table", + (int) length, name_start); + /* Create chipset name from entry */ + *seriesID = cur->seriesID; + break; + } +} + +static void parse_model_name( + const char* model_name_start, + const char* model_name_end, + struct cpuinfo_loongarch_linux_processor processor[restrict static 1]) +{ + const char* separator = model_name_start; + for (; separator != model_name_end; separator++) { + if (*separator == '-') { + break; + } + } + + const size_t model_length = (size_t) (separator - model_name_start); + const size_t name_length = (size_t) (model_name_end - (separator+1)); + + size_t value_length = name_length; + + if (value_length > CPUINFO_HARDWARE_VALUE_MAX) { + cpuinfo_log_info( + "length of model name value \"%.*s\" in /proc/cpuinfo exceeds limit (%d): truncating to the limit", + (int) value_length, separator+1, CPUINFO_HARDWARE_VALUE_MAX); + value_length = CPUINFO_HARDWARE_VALUE_MAX; + } + + cpuinfo_log_debug("parsed /proc/cpuinfo model name second value = \"%.*s\"", (int) value_length, separator+1); + + if (model_length != 8) { + cpuinfo_log_warning("Model %.*s in /proc/cpuinfo is ignored due to unexpected length (%zu)", + (int) model_length, model_name_start, separator - 1); + return; + } + if (name_length < 6 || name_length > 7) { + cpuinfo_log_warning("Model %.*s in /proc/cpuinfo is ignored due to unexpected length (%zu)", + (int) name_length, separator + 1, model_name_end); + return; + } + + /* Verify the presence of hex prefix */ + bool is_loongson = parse_loongson(model_name_start, model_length); + if (is_loongson) { + processor->prid = prid_set_companyID(processor->prid, prid_company_loongson); + processor->flags |= CPUINFO_LOONGARCH_LINUX_VALID_COMPANYID; + } else { + cpuinfo_log_warning("Model %.*s in /proc/cpuinfo is ignored due to unexpected words", + (int) model_length, model_name_start); + return; + } + + uint32_t prid_seriesID = 0; + + parse_seriesID(separator + 1, name_length, &prid_seriesID); + processor->prid = prid_set_seriesID(processor->prid, prid_seriesID); + processor->flags |= CPUINFO_LOONGARCH_LINUX_VALID_SERIESID; + +} + +static void parse_cpu_revision( + const char* cpu_revision_start, + const char* cpu_revision_end, + struct cpuinfo_loongarch_linux_processor processor[restrict static 1]) +{ + const size_t cpu_revision_length = cpu_revision_end - cpu_revision_start; + + if (cpu_revision_length != 4) { + cpuinfo_log_warning("CPU revision %.*s in /proc/cpuinfo is ignored due to unexpected length (%zu)", + (int) cpu_revision_length, cpu_revision_start, cpu_revision_length); + return; + } + + /* Skip if there is no hex prefix (0x) */ + if (cpu_revision_start[0] != '0' || cpu_revision_start[1] != 'x') { + cpuinfo_log_warning("CPU revision %.*s in /proc/cpuinfo is ignored due to lack of 0x prefix", + (int) cpu_revision_length, cpu_revision_start); + return; + } + + /* Check if the value after hex prefix is indeed a hex digit and decode it. */ + char digit_char = cpu_revision_start[2]; + uint32_t cpu_revision = 0; + if ((uint32_t) (digit_char - '0') < 10) { + cpu_revision = (uint32_t) (digit_char - '0'); + } else if ((uint32_t) (digit_char - 'A') < 6) { + cpu_revision = 10 + (uint32_t) (digit_char - 'A'); + } else if ((uint32_t) (digit_char - 'a') < 6) { + cpu_revision = 10 + (uint32_t) (digit_char - 'a'); + } else { + cpuinfo_log_warning("CPU revision %.*s in /proc/cpuinfo is ignored due to unexpected non-hex character '%c'", + (int) cpu_revision_length, cpu_revision_start, digit_char); + return; + } + cpu_revision = cpu_revision * 16; + + digit_char = cpu_revision_start[3]; + if ((uint32_t) (digit_char - '0') < 10) { + cpu_revision = (uint32_t) (digit_char - '0'); + } else if ((uint32_t) (digit_char - 'A') < 6) { + cpu_revision = 10 + (uint32_t) (digit_char - 'A'); + } else if ((uint32_t) (digit_char - 'a') < 6) { + cpu_revision = 10 + (uint32_t) (digit_char - 'a'); + } else { + cpuinfo_log_warning("CPU revision %.*s in /proc/cpuinfo is ignored due to unexpected non-hex character '%c'", + (int) cpu_revision_length, cpu_revision_start, digit_char); + return; + } + + processor->prid = prid_set_productID(processor->prid, cpu_revision); + processor->flags |= CPUINFO_LOONGARCH_LINUX_VALID_REVISION; +} + +static void parse_core( + const char* cpu_core_start, + const char* cpu_core_end, + struct cpuinfo_loongarch_linux_processor processor[restrict static 1]) +{ + uint32_t cpu_core = 0; + for (const char* digit_ptr = cpu_core_start; digit_ptr != cpu_core_end; digit_ptr++) { + const uint32_t digit = (uint32_t) (*digit_ptr - '0'); + + /* Verify that the character in core is a decimal digit */ + if (digit >= 10) { + cpuinfo_log_warning("core %.*s in /proc/cpuinfo is ignored due to unexpected non-digit character '%c' at offset %zu", + (int) (cpu_core_end - cpu_core_start), cpu_core_start, + *digit_ptr, (size_t) (digit_ptr - cpu_core_start)); + return; + } + + cpu_core = cpu_core * 10 + digit; + } + + processor->core_id = cpu_core; + processor->flags |= CPUINFO_LINUX_FLAG_CORE_ID; +} + +static void parse_package( + const char* cpu_package_start, + const char* cpu_package_end, + struct cpuinfo_loongarch_linux_processor processor[restrict static 1]) +{ + uint32_t cpu_package = 0; + for (const char* digit_ptr = cpu_package_start; digit_ptr != cpu_package_end; digit_ptr++) { + const uint32_t digit = (uint32_t) (*digit_ptr - '0'); + + /* Verify that the character in package is a decimal digit */ + if (digit >= 10) { + cpuinfo_log_warning("package %.*s in /proc/cpuinfo is ignored due to unexpected non-digit character '%c' at offset %zu", + (int) (cpu_package_end - cpu_package_start), cpu_package_start, + *digit_ptr, (size_t) (digit_ptr - cpu_package_start)); + return; + } + + cpu_package = cpu_package * 10 + digit; + } + + processor->package_id = cpu_package; + processor->flags |= CPUINFO_LINUX_FLAG_PACKAGE_ID; +} + +struct proc_cpuinfo_parser_state { + uint32_t processor_index; + uint32_t max_processors_count; + struct cpuinfo_loongarch_linux_processor* processors; + struct cpuinfo_loongarch_linux_processor dummy_processor; +}; + +/* + * Decode a single line of /proc/cpuinfo information. + * Lines have format [ ]*:[ ] + * An example of /proc/cpuinfo (from Loongson-3A5000 Loongnix20.6): + * + * system type : generic-loongson-machine + * processor : 0 + * package : 0 + * core : 0 + * cpu family : Loongson-64bit + * model name : Loongson-3A5000-HV + * CPU Revision : 0x11 + * FPU Revision : 0x00 + * CPU MHz : 2500.00 + * BogoMIPS : 5000.00 + * TLB entries : 2112 + * Address sizes : 48 bits physical, 48 bits virtual + * isa : loongarch32 loongarch64 + * features : cpucfg lam ual fpu lsx lasx crc32 lvz lbt_x86 lbt_arm lbt_mips + * hardware watchpoint : yes, iwatch count: 8, dwatch count: 8 + * + * An example of /proc/cpuinfo (from Loongson-3A6000 AOSC OS): + * system type : generic-loongson-machine + * processor : 0 + * package : 0 + * core : 1 + * global_id : 0 + * CPU Family : Loongson-64bit + * Model Name : Loongson-3A6000 + * CPU Revision : 0x00 + * FPU Revision : 0x00 + * CPU MHz : 2500.00 + * BogoMIPS : 5000.00 + * TLB Entries : 2112 + * Address Sizes : 48 bits physical, 48 bits virtual + * ISA : loongarch32r loongarch32s loongarch64 + * Features : cpucfg lam ual fpu lsx lasx crc32 complex crypto lspw lvz lbt_x86 lbt_arm lbt_mips + * Hardware Watchpoint : yes, iwatch count: 8, dwatch count: 4 + */ +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) +{ + /* Empty line. Skip. */ + if (line_start == line_end) { + return true; + } + + /* Search for ':' on the line. */ + const char* separator = line_start; + for (; separator != line_end; separator++) { + if (*separator == ':') { + break; + } + } + /* Skip line if no ':' separator was found. */ + 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; + } + + /* Skip trailing spaces in key part. */ + const char* key_end = separator; + for (; key_end != line_start; key_end--) { + if (key_end[-1] != ' ' && key_end[-1] != '\t') { + break; + } + } + /* Skip line if key contains nothing but spaces. */ + 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; + } + + /* Skip leading spaces in value part. */ + const char* value_start = separator + 1; + for (; value_start != line_end; value_start++) { + if (*value_start != ' ') { + break; + } + } + /* Value part contains nothing but spaces. Skip line. */ + 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; + } + + /* Skip trailing spaces in value part (if any) */ + 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_loongarch_linux_processor* processors = state->processors; + struct cpuinfo_loongarch_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 3: + if (strncasecmp(line_start, "isa", key_length) == 0) { + /* isa Revision is presently useless, don't parse */ + } else { + goto unknown; + } + break; + case 4: + if (strncasecmp(line_start, "core", key_length) == 0) { + parse_core(value_start, value_end, processor); + } else { + goto unknown; + } + break; + case 7: + if (strncasecmp(line_start, "package", key_length) == 0) { + parse_package(value_start, value_end, processor); + } else if (strncasecmp(line_start, "CPU MHz", key_length) == 0) { + /* CPU MHz is presently useless, don't parse */ + } else { + goto unknown; + } + break; + case 8: + if (strncasecmp(line_start, "features", key_length) == 0) { + parse_features(value_start, value_end, processor); + } else if (strncasecmp(line_start, "BogoMIPS", key_length) == 0) { + /* BogoMIPS is useless, don't parse */ + } else { + goto unknown; + } + break; + case 9: + if (strncasecmp(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) { + /* Strange: decreasing processor number */ + 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) { + /* Strange, but common: skipped processor $(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) { + /* Record that the processor was mentioned in /proc/cpuinfo */ + processors[new_processor_index].flags |= CPUINFO_LOONGARCH_LINUX_VALID_PROCESSOR; + } else { + /* Log and ignore processor */ + 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 (strncasecmp(line_start, "global_id", key_length) == 0) { + /* global_id is useless, don't parse */ + } else{ + goto unknown; + } + break; + case 10: + if (strncasecmp(line_start, "cpu family", key_length) == 0) { + /* cpu family is presently useless, don't parse */ + } else if (strncasecmp(line_start, "model name", key_length) == 0) { + memcpy(processor->hardware_name, value_start, min(value_end - value_start, CPUINFO_HARDWARE_VALUE_MAX)); + processor->hardware_name[min(value_end - value_start, CPUINFO_HARDWARE_VALUE_MAX)] = '\0'; + if (!(processor->flags & (CPUINFO_LOONGARCH_LINUX_VALID_COMPANYID | CPUINFO_LOONGARCH_LINUX_VALID_SERIESID))) + parse_model_name(value_start, value_end, processor); + } else { + goto unknown; + } + break; + case 11: + if (strncasecmp(line_start, "system type", key_length) == 0) { + /* system type is presently useless, don't parse */ + } else if (strncasecmp(line_start, "TLB entries", key_length) == 0) { + /* TLB entries is presently useless, don't parse */ + } else { + goto unknown; + } + break; + case 12: + if (strncasecmp(line_start, "CPU Revision", key_length) == 0) { + if (!(processor->flags & CPUINFO_LOONGARCH_LINUX_VALID_REVISION)) + parse_cpu_revision(value_start, value_end, processor); + } else if (strncasecmp(line_start, "FPU Revision", key_length) == 0) { + /* FPU Revision is presently useless, don't parse */ + } else { + goto unknown; + } + break; + case 13: + if (strncasecmp(line_start, "Address sizes", key_length) == 0) { + /* Address sizes is presently useless, don't parse */ + } else { + goto unknown; + } + break; + case 18: + if (strncasecmp(line_start, "hardware watchpoint", key_length) == 0) { + /* Address sizes is presently useless, don't parse */ + } else { + goto unknown; + } + break; + default: + unknown: + cpuinfo_log_debug("unknown /proc/cpuinfo key: %.*s", (int) key_length, line_start); + + } + return true; +} + +bool cpuinfo_loongarch_linux_parse_proc_cpuinfo( + uint32_t max_processors_count, + struct cpuinfo_loongarch_linux_processor processors[restrict static max_processors_count]) +{ + struct proc_cpuinfo_parser_state state = { + .processor_index = 0, + .max_processors_count = max_processors_count, + .processors = processors, + }; + cpuinfo_log_debug(""); + return cpuinfo_linux_parse_multiline_file("/proc/cpuinfo", BUFFER_SIZE, + (cpuinfo_line_callback) parse_line, &state); +} diff --git a/src/loongarch/linux/init.c b/src/loongarch/linux/init.c new file mode 100644 index 00000000..1e5ff5ce --- /dev/null +++ b/src/loongarch/linux/init.c @@ -0,0 +1,570 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +struct cpuinfo_loongarch_isa cpuinfo_isa = { 0 }; + +static inline bool bitmask_all(uint32_t bitfield, uint32_t mask) { + return (bitfield & mask) == mask; +} + +static inline uint32_t min(uint32_t a, uint32_t b) { + return a < b ? a : b; +} + +static inline int cmp(uint32_t a, uint32_t b) { + return (a > b) - (a < b); +} + +static int cmp_loongarch_linux_processor(const void* ptr_a, const void* ptr_b) { + const struct cpuinfo_loongarch_linux_processor* processor_a = (const struct cpuinfo_loongarch_linux_processor*) ptr_a; + const struct cpuinfo_loongarch_linux_processor* processor_b = (const struct cpuinfo_loongarch_linux_processor*) ptr_b; + + /* Move usable processors towards the start of the array */ + const bool usable_a = bitmask_all(processor_a->flags, CPUINFO_LINUX_FLAG_VALID); + const bool usable_b = bitmask_all(processor_b->flags, CPUINFO_LINUX_FLAG_VALID); + if (usable_a != usable_b) { + return (int) usable_b - (int) usable_a; + } + + /* Compare based on processsor ID (i.e. processor 0 < processor 1) */ + const uint32_t pro_a = processor_a->system_processor_id; + const uint32_t pro_b = processor_b->system_processor_id; + + return cmp(pro_a, pro_b); +} + +static inline bool is_cache_bit_set(enum cache_baseinfo_bit bit, uint32_t info) { + return !!((info >> bit) & 1); +} + +static inline bool set_cpuinfo_cache_by_cpucfg( + enum cpucfg_regs reg, + struct cpuinfo_cache cache[restrict static 1]) +{ + uint32_t data; + + if (!cpucfg(reg, &data)) { + return false; + } + + *cache = (struct cpuinfo_cache) { + .associativity = ((data & CACHE_WAYS_MASK) >> CACHE_WAYS_OFFSET) + 1, + .sets = 1 << ((data & CACHE_SETS_MASK) >> CACHE_SETS_OFFSET), + .line_size = 1 << ((data & CACHE_LSIZE_MASK) >> CACHE_LSIZE_OFFSET), + .partitions = 1, + }; + cache->size = cache->associativity * cache->sets * cache->line_size; + return true; +} + +static void try_set_cache_by_cpucfg( + struct cpuinfo_loongarch_linux_processor *processors, + uint32_t count) +{ + uint32_t info, flags = 0; + struct cpuinfo_cache l1i, l1d, l2, l3; + + if (!cpucfg(CPUCFG_REG_CACHE_BASEINFO, &info)) { + return; + } + + if (is_cache_bit_set(L1_IU_Present, info) && !is_cache_bit_set(L1_IU_Unify, info)) { + if (set_cpuinfo_cache_by_cpucfg(CPUCFG_REG_CACHE_L1_IU, &l1i)) { + flags |= CPUINFO_LOONGARCH_LINUX_VALID_L1I; + } + } + if (is_cache_bit_set(L1_D_Present, info)) { + if (set_cpuinfo_cache_by_cpucfg(CPUCFG_REG_CACHE_L1_D, &l1d)) { + flags |= CPUINFO_LOONGARCH_LINUX_VALID_L1D; + } + } + if (is_cache_bit_set(L2_IU_Present, info) && is_cache_bit_set(L2_IU_Unify, info)) { + if (set_cpuinfo_cache_by_cpucfg(CPUCFG_REG_CACHE_L2_IU, &l2)) { + flags |= CPUINFO_LOONGARCH_LINUX_VALID_L2; + } + } + if (is_cache_bit_set(L3_IU_Present, info) && is_cache_bit_set(L3_IU_Unify, info)) { + if (set_cpuinfo_cache_by_cpucfg(CPUCFG_REG_CACHE_L3_IU, &l3)) { + flags |= CPUINFO_LOONGARCH_LINUX_VALID_L3; + } + } + + for (uint32_t i = 0; i < count; i++) { + if (flags & CPUINFO_LOONGARCH_LINUX_VALID_L1I) { + processors[i].l1i = l1i; + } + if (flags & CPUINFO_LOONGARCH_LINUX_VALID_L1D) { + processors[i].l1d = l1d; + } + if (flags & CPUINFO_LOONGARCH_LINUX_VALID_L2) { + processors[i].l2 = l2; + } + if (flags & CPUINFO_LOONGARCH_LINUX_VALID_L3) { + processors[i].l3 = l3; + } + processors[i].flags |= flags; + } +} + +static void try_set_prid_by_cpucfg( + struct cpuinfo_loongarch_linux_processor *processors, + uint32_t count) +{ + uint32_t prid; + if (!cpucfg(CPUCFG_REG_PRID, &prid)) { + return; + } + for (uint32_t i = 0; i < count; i++) { + processors[i].prid = prid; + processors[i].flags |= CPUINFO_LOONGARCH_LINUX_VALID_PRID; + } +} + +void cpuinfo_loongarch_linux_init(void) { + struct cpuinfo_loongarch_linux_processor* loongarch_linux_processors = NULL; + struct cpuinfo_processor* processors = NULL; + struct cpuinfo_core* cores = NULL; + struct cpuinfo_cluster* clusters = NULL; + struct cpuinfo_package* packages = NULL; + struct cpuinfo_uarch_info* uarchs = NULL; + const struct cpuinfo_processor** linux_cpu_to_processor_map = NULL; + const struct cpuinfo_core** linux_cpu_to_core_map = NULL; + struct cpuinfo_cache* l1i = NULL; + struct cpuinfo_cache* l1d = NULL; + struct cpuinfo_cache* l2 = NULL; + struct cpuinfo_cache* l3 = NULL; + uint32_t* linux_cpu_to_uarch_index_map = NULL; + + const uint32_t max_processors_count = cpuinfo_linux_get_max_processors_count(); + cpuinfo_log_debug("system maximum processors count: %"PRIu32, max_processors_count); + + const uint32_t max_possible_processors_count = 1 + + cpuinfo_linux_get_max_possible_processor(max_processors_count); + cpuinfo_log_debug("maximum possible processors count: %"PRIu32, max_possible_processors_count); + const uint32_t max_present_processors_count = 1 + + cpuinfo_linux_get_max_present_processor(max_processors_count); + cpuinfo_log_debug("maximum present processors count: %"PRIu32, max_present_processors_count); + + uint32_t valid_processor_mask = CPUINFO_LOONGARCH_LINUX_VALID_PROCESSOR; + uint32_t loongarch_linux_processors_count = max_processors_count; + if (max_present_processors_count != 0) { + loongarch_linux_processors_count = min(loongarch_linux_processors_count, max_present_processors_count); + valid_processor_mask = CPUINFO_LINUX_FLAG_PRESENT; + } + if (max_possible_processors_count != 0) { + loongarch_linux_processors_count = min(loongarch_linux_processors_count, max_possible_processors_count); + valid_processor_mask |= CPUINFO_LINUX_FLAG_POSSIBLE; + } + if ((max_present_processors_count | max_possible_processors_count) == 0) { + cpuinfo_log_error("failed to parse both lists of possible and present processors"); + return; + } + + loongarch_linux_processors = calloc(loongarch_linux_processors_count, sizeof(struct cpuinfo_loongarch_linux_processor)); + if (loongarch_linux_processors == NULL) { + cpuinfo_log_error( + "failed to allocate %zu bytes for descriptions of %"PRIu32" Loongarch logical processors", + loongarch_linux_processors_count * sizeof(struct cpuinfo_loongarch_linux_processor), + loongarch_linux_processors_count); + return; + } + + if (max_possible_processors_count) { + cpuinfo_linux_detect_possible_processors( + loongarch_linux_processors_count, &loongarch_linux_processors->flags, + sizeof(struct cpuinfo_loongarch_linux_processor), + CPUINFO_LINUX_FLAG_POSSIBLE); + } + + if (max_present_processors_count) { + cpuinfo_linux_detect_present_processors( + loongarch_linux_processors_count, &loongarch_linux_processors->flags, + sizeof(struct cpuinfo_loongarch_linux_processor), + CPUINFO_LINUX_FLAG_PRESENT); + } + + #if CPUINFO_ARCH_LOONGARCH64 + /* Populate ISA structure with hwcap information. */ + cpuinfo_loongarch64_linux_decode_isa_from_hwcap(&cpuinfo_isa); + if (cpuinfo_isa.cpucfg) { + try_set_prid_by_cpucfg(loongarch_linux_processors, loongarch_linux_processors_count); + try_set_cache_by_cpucfg(loongarch_linux_processors, loongarch_linux_processors_count); + } + #endif + + /* Populate processor information. */ + uint32_t valid_processors = 0; + + if (!cpuinfo_loongarch_linux_parse_proc_cpuinfo( + loongarch_linux_processors_count, + loongarch_linux_processors)) { + cpuinfo_log_error("failed to parse processor information from /proc/cpuinfo"); + return; + } + + if (!cpuinfo_loongarch_linux_parse_cpu_cache( + loongarch_linux_processors_count, + loongarch_linux_processors)) { + cpuinfo_log_error("failed to parse processor information from /sys/devices/system/cpu/cpuX/cache/indexY/*"); + return; + } + + for (uint32_t i = 0; i < loongarch_linux_processors_count; i++) { + if (bitmask_all(loongarch_linux_processors[i].flags, valid_processor_mask)) { + valid_processors += 1; + loongarch_linux_processors[i].system_processor_id = i; + loongarch_linux_processors[i].flags |= CPUINFO_LINUX_FLAG_VALID; + continue; + } + if (!(loongarch_linux_processors[i].flags & CPUINFO_LOONGARCH_LINUX_VALID_PROCESSOR)) { + /* + * Processor is in possible and present lists, but not reported in /proc/cpuinfo. + * This is fairly common: high-index processors can be not reported if they are offline. + */ + cpuinfo_log_info("processor %"PRIu32" is not listed in /proc/cpuinfo", i); + continue; + } + /* Processor reported in /proc/cpuinfo, but not in possible and/or present lists: log and ignore */ + cpuinfo_log_warning("invalid processor %"PRIu32" reported in /proc/cpuinfo", i); + } + + /* Populate core information. */ + uint32_t last_core_id = UINT32_MAX, core_count = 0, smt_id = UINT32_MAX; + for (uint32_t i = 0; i < loongarch_linux_processors_count; i++) { + if (!bitmask_all(loongarch_linux_processors[i].flags, CPUINFO_LINUX_FLAG_VALID)) + continue; + if (!bitmask_all(loongarch_linux_processors[i].flags, CPUINFO_LINUX_FLAG_CORE_ID)) { + cpuinfo_log_warning("Not set core id for processor %"PRIu32" from /proc/cpuinfo", i); + loongarch_linux_processors[i].core_id = last_core_id; + } + smt_id += 1; + if (loongarch_linux_processors[i].core_id != last_core_id) { + core_count += 1; + smt_id = 0; + last_core_id = loongarch_linux_processors[i].core_id; + } + loongarch_linux_processors[i].smt_id = smt_id; + loongarch_linux_processors[i].flags |= CPUINFO_LINUX_FLAG_SMT_ID; + } + + /* Not populate cluster information. Thought a package as a cluster. */ + uint32_t cluster_count; + + /* Populate package information. */ + uint32_t last_package_id = UINT32_MAX, package_count = 0, package_leader_id = UINT32_MAX; + for (uint32_t i = 0; i < loongarch_linux_processors_count; i++) { + if (!bitmask_all(loongarch_linux_processors[i].flags, CPUINFO_LINUX_FLAG_VALID)) + continue; + if (!bitmask_all(loongarch_linux_processors[i].flags, CPUINFO_LINUX_FLAG_PACKAGE_ID)) { + cpuinfo_log_warning("Not set package id for processor %"PRIu32" from /proc/cpuinfo", i); + loongarch_linux_processors[i].package_id = last_package_id; + if (package_leader_id == UINT32_MAX) { + cpuinfo_log_warning("Set default package leader id 0 for processor %"PRIu32, i); + package_leader_id = 0; + } + } + if (loongarch_linux_processors[i].package_id != last_package_id) { + package_count += 1; + last_package_id = loongarch_linux_processors[i].package_id; + package_leader_id = i; + } + loongarch_linux_processors[i].package_leader_id = package_leader_id; + } + cluster_count = package_count; + + /* Initialize core vendor, uarch for every logical processor */ + for (uint32_t i = 0; i < loongarch_linux_processors_count; i++) { + if (!bitmask_all(loongarch_linux_processors[i].flags, CPUINFO_LINUX_FLAG_VALID | CPUINFO_LINUX_FLAG_PACKAGE_ID)) + continue; + const uint32_t package_leader = loongarch_linux_processors[i].package_leader_id; + if (package_leader == i) { + /* Package leader: decode core vendor and uarch */ + cpuinfo_loongarch_decode_vendor_uarch( + loongarch_linux_processors[package_leader].prid, + &loongarch_linux_processors[package_leader].vendor, + &loongarch_linux_processors[package_leader].uarch); + } else { + /* Package non-leader: copy vendor, uarch from package leader */ + loongarch_linux_processors[i].vendor = loongarch_linux_processors[package_leader].vendor; + loongarch_linux_processors[i].uarch = loongarch_linux_processors[package_leader].uarch; + } + } + + qsort(loongarch_linux_processors, loongarch_linux_processors_count, + sizeof(struct cpuinfo_loongarch_linux_processor), cmp_loongarch_linux_processor); + + + uint32_t uarchs_count = 0; + enum cpuinfo_uarch last_uarch = cpuinfo_uarch_unknown; + for (uint32_t i = 0; i < loongarch_linux_processors_count; i++) { + if (!bitmask_all(loongarch_linux_processors[i].flags, CPUINFO_LINUX_FLAG_VALID)) + continue; + if (uarchs_count == 0 || loongarch_linux_processors[i].uarch != last_uarch) { + last_uarch = loongarch_linux_processors[i].uarch; + uarchs_count += 1; + } + loongarch_linux_processors[i].uarch_index = uarchs_count - 1; + } + + processors = calloc(valid_processors, sizeof(struct cpuinfo_processor)); + if (processors == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for descriptions of %"PRIu32" logical processors", + valid_processors * sizeof(struct cpuinfo_processor), valid_processors); + goto cleanup; + } + + cores = calloc(core_count, sizeof(struct cpuinfo_core)); + if (cores == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for descriptions of %"PRIu32" cores", + core_count * sizeof(struct cpuinfo_core), core_count); + goto cleanup; + } + + clusters = calloc(cluster_count, sizeof(struct cpuinfo_cluster)); + if (clusters == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for descriptions of %"PRIu32" core clusters", + cluster_count * sizeof(struct cpuinfo_cluster), cluster_count); + goto cleanup; + } + + packages = calloc(cluster_count, sizeof(struct cpuinfo_package)); + if (packages == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for descriptions of %"PRIu32" core packages", + cluster_count * sizeof(struct cpuinfo_package), package_count); + goto cleanup; + } + + uarchs = calloc(uarchs_count, sizeof(struct cpuinfo_uarch_info)); + if (uarchs == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for descriptions of %"PRIu32" microarchitectures", + uarchs_count * sizeof(struct cpuinfo_uarch_info), uarchs_count); + goto cleanup; + } + + linux_cpu_to_processor_map = calloc(loongarch_linux_processors_count, sizeof(struct cpuinfo_processor*)); + if (linux_cpu_to_processor_map == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for %"PRIu32" logical processor mapping entries", + loongarch_linux_processors_count * sizeof(struct cpuinfo_processor*), loongarch_linux_processors_count); + goto cleanup; + } + + linux_cpu_to_core_map = calloc(loongarch_linux_processors_count, sizeof(struct cpuinfo_core*)); + if (linux_cpu_to_core_map == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for %"PRIu32" core mapping entries", + loongarch_linux_processors_count * sizeof(struct cpuinfo_core*), loongarch_linux_processors_count); + goto cleanup; + } + + if (uarchs_count > 1) { + linux_cpu_to_uarch_index_map = calloc(loongarch_linux_processors_count, sizeof(uint32_t)); + if (linux_cpu_to_uarch_index_map == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for %"PRIu32" uarch index mapping entries", + loongarch_linux_processors_count * sizeof(uint32_t), loongarch_linux_processors_count); + goto cleanup; + } + } + + l1i = calloc(valid_processors, sizeof(struct cpuinfo_cache)); + if (l1i == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for descriptions of %"PRIu32" L1I caches", + valid_processors * sizeof(struct cpuinfo_cache), valid_processors); + goto cleanup; + } + + l1d = calloc(valid_processors, sizeof(struct cpuinfo_cache)); + if (l1d == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for descriptions of %"PRIu32" L1D caches", + valid_processors * sizeof(struct cpuinfo_cache), valid_processors); + goto cleanup; + } + + // Victim Cache is private. TODO dynamic detect + l2 = calloc(valid_processors, sizeof(struct cpuinfo_cache)); + if (l2 == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for descriptions of %"PRIu32" L2 caches", + valid_processors * sizeof(struct cpuinfo_cache), valid_processors); + goto cleanup; + } + + // Shared in package. + l3 = calloc(package_count, sizeof(struct cpuinfo_cache)); + if (l3 == NULL) { + cpuinfo_log_error("failed to allocate %zu bytes for descriptions of %"PRIu32" L2 caches", + valid_processors * sizeof(struct cpuinfo_cache), valid_processors); + goto cleanup; + } + + uint32_t uarchs_index = 0; + last_uarch = cpuinfo_uarch_unknown; + for (uint32_t i = 0; i < loongarch_linux_processors_count; i++) { + if (!bitmask_all(loongarch_linux_processors[i].flags, CPUINFO_LINUX_FLAG_VALID)) { + continue; + } + if (uarchs_index == 0 || loongarch_linux_processors[i].uarch != last_uarch) { + last_uarch = loongarch_linux_processors[i].uarch; + uarchs[uarchs_index] = (struct cpuinfo_uarch_info) { + .uarch = loongarch_linux_processors[i].uarch, + .prid = loongarch_linux_processors[i].prid, + }; + uarchs_index += 1; + } + uarchs[uarchs_index - 1].processor_count += 1; + uarchs[uarchs_index - 1].core_count += loongarch_linux_processors[i].smt_id == 0 ? 1 : 0; + } + + /* Transfer contents of processor list to ABI structures. */ + uint32_t processor_index = UINT32_MAX, core_index = UINT32_MAX, cluster_index = UINT32_MAX, package_index = UINT32_MAX; + last_core_id = last_package_id = UINT32_MAX; + for (uint32_t i = 0; i < loongarch_linux_processors_count; i++) { + struct cpuinfo_loongarch_linux_processor *cur = &loongarch_linux_processors[i]; + + if (!bitmask_all(cur->flags, CPUINFO_LINUX_FLAG_VALID)) { + continue; + } + + processor_index += 1; + core_index += last_core_id != cur->core_id ? 1 : 0; + cluster_index += last_package_id != cur->package_id ? 1 : 0; + package_index += last_package_id != cur->package_id ? 1 : 0; + processors[processor_index] = (struct cpuinfo_processor) { + .smt_id = cur->smt_id, + .core = &cores[core_index], + .cluster = &clusters[cluster_index], + .package = &packages[package_index], + .linux_id = (int) cur->system_processor_id, + .cache.l1i = &l1i[processor_index], + .cache.l1d = &l1d[processor_index], + .cache.l2 = &l2[processor_index], + .cache.l3 = &l3[package_index], + }; + + memcpy(&l1i[processor_index], &cur->l1i, sizeof(struct cpuinfo_cache)); + memcpy(&l1d[processor_index], &cur->l1d, sizeof(struct cpuinfo_cache)); + memcpy(&l2[processor_index], &cur->l2, sizeof(struct cpuinfo_cache)); + l1i[processor_index].processor_start = processor_index; + l1i[processor_index].processor_count = 1; + l1d[processor_index].processor_start = processor_index; + l1d[processor_index].processor_count = 1; + l2[processor_index].processor_start = processor_index; + l2[processor_index].processor_count = 1; + + if (cur->smt_id == 0) { + cores[core_index] = (struct cpuinfo_core) { + .processor_start = processor_index, + .processor_count = 1, + .core_id = cur->core_id, + .cluster = &clusters[cluster_index], + .package = &packages[package_index], + .vendor = cur->vendor, + .uarch = cur->uarch, + .prid = cur->prid, + }; + last_core_id = cur->core_id; + } else { + cores[core_index].processor_count += 1; + } + + if (cur->package_leader_id == cur->system_processor_id) { + clusters[cluster_index] = (struct cpuinfo_cluster) { + .processor_start = processor_index, + .processor_count = 1, + .core_start = core_index, + .core_count = 1, + .cluster_id = 0, // Thought a package as a cluster + .package = &packages[package_index], + .vendor = cur->vendor, + .uarch = cur->uarch, + .prid = cur->prid, + }; + packages[package_index] = (struct cpuinfo_package) { + .processor_start = processor_index, + .processor_count = 1, + .core_start = core_index, + .core_count = 1, + .cluster_start = cluster_index, + .cluster_count = 1, + }; + memcpy(&l3[package_index], &cur->l3, sizeof(struct cpuinfo_cache)); + l3[package_index].processor_start = processor_index; + l3[package_index].processor_count = 1; + last_package_id = cur->package_id; + } else { + clusters[cluster_index].processor_count += 1; + clusters[cluster_index].core_count += cur->smt_id == 0 ? 1 : 0; + packages[package_index].processor_count += 1; + packages[package_index].core_count += cur->smt_id == 0 ? 1 : 0; + l3[package_index].processor_count += 1; + } + + linux_cpu_to_processor_map[cur->system_processor_id] = &processors[processor_index]; + linux_cpu_to_core_map[cur->system_processor_id] = &cores[core_index]; + if (linux_cpu_to_uarch_index_map != NULL) { + linux_cpu_to_uarch_index_map[cur->system_processor_id] = cur->uarch_index; + } + } + + /* Commit */ + cpuinfo_processors = processors; + cpuinfo_cores = cores; + cpuinfo_clusters = clusters; + cpuinfo_packages = packages; + cpuinfo_uarchs = uarchs; + cpuinfo_cache[cpuinfo_cache_level_1i] = l1i; + cpuinfo_cache[cpuinfo_cache_level_1d] = l1d; + cpuinfo_cache[cpuinfo_cache_level_2] = l2; + cpuinfo_cache[cpuinfo_cache_level_3] = l3; + + cpuinfo_processors_count = valid_processors; + cpuinfo_cores_count = core_count; + cpuinfo_clusters_count = cluster_count; + cpuinfo_packages_count = package_count; + cpuinfo_uarchs_count = uarchs_count; + cpuinfo_cache_count[cpuinfo_cache_level_1i] = valid_processors; + cpuinfo_cache_count[cpuinfo_cache_level_1d] = valid_processors; + cpuinfo_cache_count[cpuinfo_cache_level_2] = valid_processors; + cpuinfo_cache_count[cpuinfo_cache_level_3] = package_count; + cpuinfo_max_cache_size = cpuinfo_compute_max_cache_size(&processors[0]); + + cpuinfo_linux_cpu_max = loongarch_linux_processors_count; + cpuinfo_linux_cpu_to_processor_map = linux_cpu_to_processor_map; + cpuinfo_linux_cpu_to_core_map = linux_cpu_to_core_map; + cpuinfo_linux_cpu_to_uarch_index_map = linux_cpu_to_uarch_index_map; + + __sync_synchronize(); + cpuinfo_is_initialized = true; + + processors = NULL; + cores = NULL; + clusters = NULL; + uarchs = NULL; + l1i = l1d = l2 = l3 = NULL; + linux_cpu_to_processor_map = NULL; + linux_cpu_to_core_map = NULL; + linux_cpu_to_uarch_index_map = NULL; + +cleanup: + free(loongarch_linux_processors); + free(processors); + free(cores); + free(clusters); + free(uarchs); + free(l1i); + free(l1d); + free(l2); + free(l3); + free(linux_cpu_to_processor_map); + free(linux_cpu_to_core_map); + free(linux_cpu_to_uarch_index_map); +} diff --git a/src/loongarch/linux/loongarch64-isa.c b/src/loongarch/linux/loongarch64-isa.c new file mode 100644 index 00000000..a89e5f52 --- /dev/null +++ b/src/loongarch/linux/loongarch64-isa.c @@ -0,0 +1,31 @@ +#include +#include + +#include + +static inline uint32_t hwcap_from_getauxval() +{ + return (uint32_t) getauxval(AT_HWCAP); +} + +void cpuinfo_loongarch64_linux_decode_isa_from_hwcap( + struct cpuinfo_loongarch_isa isa[restrict static 1]) +{ + uint32_t features = hwcap_from_getauxval(); +#define ISA_ENABLE(BIT, FLAG) isa->BIT = !!(features & CPUINFO_LOONGARCH_LINUX_FEATURE_##FLAG) + ISA_ENABLE(cpucfg, CPUCFG); + ISA_ENABLE(lam, LAM); + ISA_ENABLE(ual, UAL); + ISA_ENABLE(fpu, FPU); + ISA_ENABLE(lsx, LSX); + ISA_ENABLE(lasx, LASX); + ISA_ENABLE(crc32, CRC32); + ISA_ENABLE(complex, COMPLEX); + ISA_ENABLE(crypto, CRYPTO); + ISA_ENABLE(lvz, LVZ); + ISA_ENABLE(lbt_x86, LBT_X86); + ISA_ENABLE(lbt_arm, LBT_ARM); + ISA_ENABLE(lbt_mips, LBT_MIPS); + ISA_ENABLE(ptw, PTW); + ISA_ENABLE(lspw, LSPW); +} diff --git a/src/loongarch/prid.h b/src/loongarch/prid.h new file mode 100644 index 00000000..57841ea7 --- /dev/null +++ b/src/loongarch/prid.h @@ -0,0 +1,65 @@ +#pragma once +#include + +/* + * LoongArch can get PRID by `cpucfg 0` on LoongArch expect LA32R. + * This file support interface to construct PRID. + * + * Linux: arch/loongarch/include/asm/cpu.h + * As described in LoongArch specs from Loongson Technology, the PRID register + * (CPUCFG.00) has the following layout: + * + * +---------------+----------------+------------+--------------------+ + * | Reserved | Company ID | Series ID | Product ID | + * +---------------+----------------+------------+--------------------+ + * 31 24 23 16 15 12 11 0 + */ + +#define CPUINFO_LOONGARCH_PRID_COMPANY_MASK UINT32_C(0x00FF0000) +#define CPUINFO_LOONGARCH_PRID_SERIES_MASK UINT32_C(0x0000F000) +#define CPUINFO_LOONGARCH_PRID_PRODUCT_MASK UINT32_C(0x00000FFF) + +#define CPUINFO_LOONGARCH_PRID_COMPANY_OFFSET 16 +#define CPUINFO_LOONGARCH_PRID_SERIES_OFFSET 12 +#define CPUINFO_LOONGARCH_PRID_PRODUCT_OFFSET 0 + +#define PRID_COMP_LOONGSON 0x140000 + +enum prid_company { + prid_company_loongson = 0x14, /* Loongson Technology */ +}; + +enum prid_series { + prid_series_la132 = 0x8, /* Loongson 32bit */ + prid_series_la264 = 0xa, /* Loongson 64bit, 2-issue */ + prid_series_la364 = 0xb, /* Loongson 64bit, 3-issue */ + prid_series_la464 = 0xc, /* Loongson 64bit, 4-issue */ + prid_series_la664 = 0xd, /* Loongson 64bit, 6-issue */ +}; + +inline static uint32_t prid_set_companyID(uint32_t prid, uint32_t companyID) { + return (prid & ~CPUINFO_LOONGARCH_PRID_COMPANY_MASK) | + ((companyID << CPUINFO_LOONGARCH_PRID_COMPANY_OFFSET) & CPUINFO_LOONGARCH_PRID_COMPANY_MASK); +} + +inline static uint32_t prid_set_seriesID(uint32_t prid, uint32_t seriesID) { + return (prid & ~CPUINFO_LOONGARCH_PRID_SERIES_MASK) | + ((seriesID << CPUINFO_LOONGARCH_PRID_SERIES_OFFSET) & CPUINFO_LOONGARCH_PRID_SERIES_MASK); +} + +inline static uint32_t prid_set_productID(uint32_t prid, uint32_t productID) { + return (prid & ~CPUINFO_LOONGARCH_PRID_PRODUCT_MASK) | + ((productID << CPUINFO_LOONGARCH_PRID_PRODUCT_OFFSET) & CPUINFO_LOONGARCH_PRID_PRODUCT_MASK); +} + +inline static uint32_t prid_get_companyID(uint32_t prid) { + return (prid & CPUINFO_LOONGARCH_PRID_COMPANY_MASK) >> CPUINFO_LOONGARCH_PRID_COMPANY_OFFSET; +} + +inline static uint32_t prid_get_seriesID(uint32_t prid) { + return (prid & CPUINFO_LOONGARCH_PRID_SERIES_MASK) >> CPUINFO_LOONGARCH_PRID_SERIES_OFFSET; +} + +inline static uint32_t prid_get_productID(uint32_t prid) { + return (prid & CPUINFO_LOONGARCH_PRID_PRODUCT_MASK) >> CPUINFO_LOONGARCH_PRID_PRODUCT_OFFSET; +} diff --git a/src/loongarch/uarch.c b/src/loongarch/uarch.c new file mode 100644 index 00000000..6cd7382e --- /dev/null +++ b/src/loongarch/uarch.c @@ -0,0 +1,53 @@ +#include + +#include +#include +#include + +static void cpuinfo_loongarch_decode_vendor( + uint32_t companyID, + enum cpuinfo_vendor vendor[restrict static 1]) +{ + switch (companyID) { + case prid_company_loongson: + *vendor = cpuinfo_vendor_loongson; + break; + default: + *vendor = cpuinfo_vendor_unknown; + break; + } +} + +static void cpuinfo_loongarch_decode_uarch( + uint32_t seriesID, + enum cpuinfo_uarch uarch[restrict static 1]) +{ + switch (seriesID) { + case prid_series_la264: + *uarch = cpuinfo_uarch_LA264; + break; + case prid_series_la364: + *uarch = cpuinfo_uarch_LA364; + break; + case prid_series_la464: + *uarch = cpuinfo_uarch_LA464; + break; + case prid_series_la664: + *uarch = cpuinfo_uarch_LA664; + break; + default: + *uarch = cpuinfo_uarch_unknown; + break; + } +} + +void cpuinfo_loongarch_decode_vendor_uarch( + uint32_t prid, + enum cpuinfo_vendor vendor[restrict static 1], + enum cpuinfo_uarch uarch[restrict static 1]) +{ + cpuinfo_loongarch_decode_vendor( + prid_get_companyID(prid), vendor); + cpuinfo_loongarch_decode_uarch( + prid_get_seriesID(prid), uarch); +} diff --git a/tools/cpu-info.c b/tools/cpu-info.c index b896b270..252dad8a 100644 --- a/tools/cpu-info.c +++ b/tools/cpu-info.c @@ -41,6 +41,8 @@ static const char* vendor_to_string(enum cpuinfo_vendor vendor) { return "Broadcom"; case cpuinfo_vendor_apm: return "Applied Micro"; + case cpuinfo_vendor_loongson: + return "Loongson"; default: return NULL; } @@ -284,6 +286,8 @@ static const char* uarch_to_string(enum cpuinfo_uarch uarch) { return "Dhyana"; case cpuinfo_uarch_taishan_v110: return "TaiShan v110"; + case cpuinfo_uarch_LA464: + return "LA464"; default: return NULL; } diff --git a/tools/isa-info.c b/tools/isa-info.c index 96bcdd7a..722f382e 100644 --- a/tools/isa-info.c +++ b/tools/isa-info.c @@ -193,4 +193,26 @@ int main(int argc, char** argv) { printf("\tCompressed: %s\n", cpuinfo_has_riscv_c() ? "yes" : "no"); printf("\tVector: %s\n", cpuinfo_has_riscv_v() ? "yes" : "no"); #endif +#if CPUINFO_ARCH_LOONGARCH64 + printf("Loongarch:\n"); + printf("\tCPUCFG: %s\n", cpuinfo_has_loongarch_cpucfg() ? "yes" : "no"); + printf("\tLAM: %s\n", cpuinfo_has_loongarch_lam() ? "yes" : "no"); + printf("\tUAL: %s\n", cpuinfo_has_loongarch_ual() ? "yes" : "no"); + printf("\tFPU: %s\n", cpuinfo_has_loongarch_fpu() ? "yes" : "no"); + printf("\tCOMPLEX: %s\n", cpuinfo_has_loongarch_complex() ? "yes" : "no"); + printf("\tLVZ: %s\n", cpuinfo_has_loongarch_lvz() ? "yes" : "no"); + printf("\tLBT_X86: %s\n", cpuinfo_has_loongarch_lbt_x86() ? "yes" : "no"); + printf("\tLBT_arm: %s\n", cpuinfo_has_loongarch_lbt_arm() ? "yes" : "no"); + printf("\tLBT_mips: %s\n", cpuinfo_has_loongarch_lbt_mips() ? "yes" : "no"); + printf("\tPTW: %s\n", cpuinfo_has_loongarch_ptw() ? "yes" : "no"); + printf("\tLSPW: %s\n", cpuinfo_has_loongarch_lspw() ? "yes" : "no"); + + printf("SIMD extensions:\n"); + printf("\tLSX: %s\n", cpuinfo_has_loongarch_lsx() ? "yes" : "no"); + printf("\tLASX: %s\n", cpuinfo_has_loongarch_lasx() ? "yes" : "no"); + + printf("Cryptography extensions:\n"); + printf("\tCRYPTO: %s\n", cpuinfo_has_loongarch_crypto() ? "yes" : "no"); + printf("\tCRC32: %s\n", cpuinfo_has_loongarch_crc32() ? "yes" : "no"); +#endif }