| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <cpuinfo.h> |
| #include <cpuinfo/common.h> |
| #include <x86/api.h> |
| |
| /* The state of the parser to be preserved between parsing different tokens. */ |
| struct parser_state { |
| /* |
| * Pointer to the start of the previous token if it is "model". |
| * NULL if previous token is not "model". |
| */ |
| char* context_model; |
| /* |
| * Pointer to the start of the previous token if it is a |
| * single-uppercase-letter token. NULL if previous token is anything |
| * different. |
| */ |
| char* context_upper_letter; |
| /* |
| * Pointer to the start of the previous token if it is "Dual". |
| * NULL if previous token is not "Dual". |
| */ |
| char* context_dual; |
| /* |
| * Pointer to the start of the previous token if it is "Core", |
| * "Dual-Core", "QuadCore", etc. NULL if previous token is anything |
| * different. |
| */ |
| char* context_core; |
| /* |
| * Pointer to the start of the previous token if it is "Eng" or |
| * "Engineering", etc. NULL if previous token is anything different. |
| */ |
| char* context_engineering; |
| /* |
| * Pointer to the '@' symbol in the brand string (separates frequency |
| * specification). NULL if there is no '@' symbol. |
| */ |
| char* frequency_separator; |
| /* Indicates whether the brand string (after transformations) contains |
| * frequency. */ |
| bool frequency_token; |
| /* Indicates whether the processor is of Xeon family (contains "Xeon" |
| * substring). */ |
| bool xeon; |
| /* Indicates whether the processor model number was already parsed. */ |
| bool parsed_model_number; |
| /* Indicates whether the processor is an engineering sample (contains |
| * "Engineering Sample" or "Eng Sample" substrings). */ |
| bool engineering_sample; |
| }; |
| |
| /** @brief Resets information about the previous token. Keeps all other |
| * state information. */ |
| static void reset_context(struct parser_state* state) { |
| state->context_model = NULL; |
| state->context_upper_letter = NULL; |
| state->context_dual = NULL; |
| state->context_core = NULL; |
| } |
| |
| /** |
| * @brief Overwrites the supplied string with space characters if it |
| * exactly matches the given string. |
| * @param string The string to be compared against other string, and |
| * erased in case of matching. |
| * @param length The length of the two string to be compared against each |
| * other. |
| * @param target The string to compare against. |
| * @retval true If the two strings match and the first supplied string |
| * was erased (overwritten with space characters). |
| * @retval false If the two strings are different and the first supplied |
| * string remained unchanged. |
| */ |
| static inline bool erase_matching(char* string, size_t length, const char* target) { |
| const bool match = memcmp(string, target, length) == 0; |
| if (match) { |
| memset(string, ' ', length); |
| } |
| return match; |
| } |
| |
| /** |
| * @brief Checks if the supplied ASCII character is an uppercase latin |
| * letter. |
| * @param character The character to analyse. |
| * @retval true If the supplied character is an uppercase latin letter |
| * ('A' to 'Z'). |
| * @retval false If the supplied character is anything different. |
| */ |
| static inline bool is_upper_letter(char character) { |
| return (uint32_t)(character - 'A') <= (uint32_t)('Z' - 'A'); |
| } |
| |
| /** |
| * @brief Checks if the supplied ASCII character is a digit. |
| * @param character The character to analyse. |
| * @retval true If the supplied character is a digit ('0' to '9'). |
| * @retval false If the supplied character is anything different. |
| */ |
| static inline bool is_digit(char character) { |
| return (uint32_t)(character - '0') < UINT32_C(10); |
| } |
| |
| static inline bool is_zero_number(const char* token_start, const char* token_end) { |
| for (const char* char_ptr = token_start; char_ptr != token_end; char_ptr++) { |
| if (*char_ptr != '0') { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static inline bool is_space(const char* token_start, const char* token_end) { |
| for (const char* char_ptr = token_start; char_ptr != token_end; char_ptr++) { |
| if (*char_ptr != ' ') { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static inline bool is_number(const char* token_start, const char* token_end) { |
| for (const char* char_ptr = token_start; char_ptr != token_end; char_ptr++) { |
| if (!is_digit(*char_ptr)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static inline bool is_model_number(const char* token_start, const char* token_end) { |
| for (const char* char_ptr = token_start + 1; char_ptr < token_end; char_ptr++) { |
| if (is_digit(char_ptr[-1]) && is_digit(char_ptr[0])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static inline bool is_frequency(const char* token_start, const char* token_end) { |
| const size_t token_length = (size_t)(token_end - token_start); |
| if (token_length > 3 && token_end[-2] == 'H' && token_end[-1] == 'z') { |
| switch (token_end[-3]) { |
| case 'K': |
| case 'M': |
| case 'G': |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @warning Input and output tokens can overlap |
| */ |
| static inline char* move_token(const char* token_start, const char* token_end, char* output_ptr) { |
| const size_t token_length = (size_t)(token_end - token_start); |
| memmove(output_ptr, token_start, token_length); |
| return output_ptr + token_length; |
| } |
| |
| static bool transform_token(char* token_start, char* token_end, struct parser_state* state) { |
| const struct parser_state previousState = *state; |
| reset_context(state); |
| |
| size_t token_length = (size_t)(token_end - token_start); |
| |
| if (state->frequency_separator != NULL) { |
| if (token_start > state->frequency_separator) { |
| if (state->parsed_model_number) { |
| memset(token_start, ' ', token_length); |
| } |
| } |
| } |
| |
| /* Early AMD and Cyrix processors have "tm" suffix for trademark, e.g. |
| * "AMD-K6tm w/ multimedia extensions" |
| * "Cyrix MediaGXtm MMXtm Enhanced" |
| */ |
| if (token_length > 2) { |
| const char context_char = token_end[-3]; |
| if (is_digit(context_char) || is_upper_letter(context_char)) { |
| if (erase_matching(token_end - 2, 2, "tm")) { |
| token_end -= 2; |
| token_length -= 2; |
| } |
| } |
| } |
| if (token_length > 4) { |
| /* Some early AMD CPUs have "AMD-" at the beginning, e.g. |
| * "AMD-K5(tm) Processor" |
| * "AMD-K6tm w/ multimedia extensions" |
| * "AMD-K6(tm) 3D+ Processor" |
| * "AMD-K6(tm)-III Processor" |
| */ |
| if (erase_matching(token_start, 4, "AMD-")) { |
| token_start += 4; |
| token_length -= 4; |
| } |
| } |
| switch (token_length) { |
| case 1: |
| /* |
| * On some Intel processors there is a space between the |
| * first letter of the name and the number after it, |
| * e.g. "Intel(R) Core(TM) i7 CPU X 990 @ 3.47GHz" |
| * "Intel(R) Core(TM) CPU Q 820 @ 1.73GHz" |
| * We want to merge these parts together, in reverse |
| * order, i.e. "X 990" |
| * -> "990X", "820" -> "820Q" |
| */ |
| if (is_upper_letter(token_start[0])) { |
| state->context_upper_letter = token_start; |
| return true; |
| } |
| break; |
| case 2: |
| /* Erase everything after "w/" in "AMD-K6tm w/ |
| * multimedia extensions" */ |
| if (erase_matching(token_start, token_length, "w/")) { |
| return false; |
| } |
| /* |
| * Intel Xeon processors since Ivy Bridge use versions, |
| * e.g. "Intel Xeon E3-1230 v2" Some processor branch |
| * strings report them as "V<N>", others report as |
| * "v<N>". Normalize the former (upper-case) to the |
| * latter (lower-case) version |
| */ |
| if (token_start[0] == 'V' && is_digit(token_start[1])) { |
| token_start[0] = 'v'; |
| return true; |
| } |
| break; |
| case 3: |
| /* |
| * Erase "CPU" in brand string on Intel processors, e.g. |
| * "Intel(R) Core(TM) i5 CPU 650 @ 3.20GHz" |
| * "Intel(R) Xeon(R) CPU X3210 @ 2.13GHz" |
| * "Intel(R) Atom(TM) CPU Z2760 @ 1.80GHz" |
| */ |
| if (erase_matching(token_start, token_length, "CPU")) { |
| return true; |
| } |
| /* |
| * Erase everything after "SOC" on AMD System-on-Chips, |
| * e.g. "AMD GX-212JC SOC with Radeon(TM) R2E Graphics |
| * \0" |
| */ |
| if (erase_matching(token_start, token_length, "SOC")) { |
| return false; |
| } |
| /* |
| * Erase "AMD" in brand string on AMD processors, e.g. |
| * "AMD Athlon(tm) Processor" |
| * "AMD Engineering Sample" |
| * "Quad-Core AMD Opteron(tm) Processor 2344 HE" |
| */ |
| if (erase_matching(token_start, token_length, "AMD")) { |
| return true; |
| } |
| /* |
| * Erase "VIA" in brand string on VIA processors, e.g. |
| * "VIA C3 Ezra" |
| * "VIA C7-M Processor 1200MHz" |
| * "VIA Nano L3050@1800MHz" |
| */ |
| if (erase_matching(token_start, token_length, "VIA")) { |
| return true; |
| } |
| /* Erase "IDT" in brand string on early Centaur |
| * processors, e.g. "IDT WinChip 2-3D" */ |
| if (erase_matching(token_start, token_length, "IDT")) { |
| return true; |
| } |
| /* |
| * Erase everything starting with "MMX" in |
| * "Cyrix MediaGXtm MMXtm Enhanced" ("tm" suffix is |
| * removed by this point) |
| */ |
| if (erase_matching(token_start, token_length, "MMX")) { |
| return false; |
| } |
| /* |
| * Erase everything starting with "APU" on AMD |
| * processors, e.g. "AMD A10-4600M APU with Radeon(tm) |
| * HD Graphics" "AMD A10-7850K APU with Radeon(TM) R7 |
| * Graphics" "AMD A6-6310 APU with AMD Radeon R4 |
| * Graphics" |
| */ |
| if (erase_matching(token_start, token_length, "APU")) { |
| return false; |
| } |
| /* |
| * Remember to discard string if it contains "Eng |
| * Sample", e.g. "Eng Sample, |
| * ZD302046W4K43_36/30/20_2/8_A" |
| */ |
| if (memcmp(token_start, "Eng", token_length) == 0) { |
| state->context_engineering = token_start; |
| } |
| break; |
| case 4: |
| /* Remember to erase "Dual Core" in "AMD Athlon(tm) 64 |
| * X2 Dual Core Processor 3800+" */ |
| if (memcmp(token_start, "Dual", token_length) == 0) { |
| state->context_dual = token_start; |
| } |
| /* Remember if the processor is on Xeon family */ |
| if (memcmp(token_start, "Xeon", token_length) == 0) { |
| state->xeon = true; |
| } |
| /* Erase "Dual Core" in "AMD Athlon(tm) 64 X2 Dual Core |
| * Processor 3800+" |
| */ |
| if (previousState.context_dual != NULL) { |
| if (memcmp(token_start, "Core", token_length) == 0) { |
| memset(previousState.context_dual, |
| ' ', |
| (size_t)(token_end - previousState.context_dual)); |
| state->context_core = token_end; |
| return true; |
| } |
| } |
| break; |
| case 5: |
| /* |
| * Erase "Intel" in brand string on Intel processors, |
| * e.g. "Intel(R) Xeon(R) CPU X3210 @ 2.13GHz" "Intel(R) |
| * Atom(TM) CPU D2700 @ 2.13GHz" "Genuine Intel(R) |
| * processor 800MHz" |
| */ |
| if (erase_matching(token_start, token_length, "Intel")) { |
| return true; |
| } |
| /* |
| * Erase "Cyrix" in brand string on Cyrix processors, |
| * e.g. "Cyrix MediaGXtm MMXtm Enhanced" |
| */ |
| if (erase_matching(token_start, token_length, "Cyrix")) { |
| return true; |
| } |
| /* |
| * Erase everything following "Geode" (but not "Geode" |
| * token itself) on Geode processors, e.g. "Geode(TM) |
| * Integrated Processor by AMD PCS" "Geode(TM) |
| * Integrated Processor by National Semi" |
| */ |
| if (memcmp(token_start, "Geode", token_length) == 0) { |
| return false; |
| } |
| /* Remember to erase "model unknown" in "AMD Processor |
| * model unknown" */ |
| if (memcmp(token_start, "model", token_length) == 0) { |
| state->context_model = token_start; |
| return true; |
| } |
| break; |
| case 6: |
| /* |
| * Erase everything starting with "Radeon" or "RADEON" |
| * on AMD APUs, e.g. "A8-7670K Radeon R7, 10 Compute |
| * Cores 4C+6G" "FX-8800P Radeon R7, 12 Compute Cores |
| * 4C+8G" "A12-9800 RADEON R7, 12 COMPUTE CORES 4C+8G" |
| * "A9-9410 RADEON R5, 5 COMPUTE CORES 2C+3G" |
| */ |
| if (erase_matching(token_start, token_length, "Radeon") || |
| erase_matching(token_start, token_length, "RADEON")) { |
| return false; |
| } |
| /* |
| * Erase "Mobile" when it is not part of the processor |
| * name, e.g. in "AMD Turion(tm) X2 Ultra Dual-Core |
| * Mobile ZM-82" |
| */ |
| if (previousState.context_core != NULL) { |
| if (erase_matching(token_start, token_length, "Mobile")) { |
| return true; |
| } |
| } |
| /* Erase "family" in "Intel(R) Pentium(R) III CPU family |
| * 1266MHz" */ |
| if (erase_matching(token_start, token_length, "family")) { |
| return true; |
| } |
| /* Discard the string if it contains "Engineering |
| * Sample" */ |
| if (previousState.context_engineering != NULL) { |
| if (memcmp(token_start, "Sample", token_length) == 0) { |
| state->engineering_sample = true; |
| return false; |
| } |
| } |
| break; |
| case 7: |
| /* |
| * Erase "Geniune" in brand string on Intel engineering |
| * samples, e.g. "Genuine Intel(R) processor 800MHz" |
| * "Genuine Intel(R) CPU @ 2.13GHz" |
| * "Genuine Intel(R) CPU 0000 @ 1.73GHz" |
| */ |
| if (erase_matching(token_start, token_length, "Genuine")) { |
| return true; |
| } |
| /* |
| * Erase "12-core" in brand string on AMD Threadripper, |
| * e.g. "AMD Ryzen Threadripper 1920X 12-Core Processor" |
| */ |
| if (erase_matching(token_start, token_length, "12-Core")) { |
| return true; |
| } |
| /* |
| * Erase "16-core" in brand string on AMD Threadripper, |
| * e.g. "AMD Ryzen Threadripper 1950X 16-Core Processor" |
| */ |
| if (erase_matching(token_start, token_length, "16-Core")) { |
| return true; |
| } |
| /* Erase "model unknown" in "AMD Processor model |
| * unknown" */ |
| if (previousState.context_model != NULL) { |
| if (memcmp(token_start, "unknown", token_length) == 0) { |
| memset(previousState.context_model, |
| ' ', |
| token_end - previousState.context_model); |
| return true; |
| } |
| } |
| /* |
| * Discard the string if it contains "Eng Sample:" or |
| * "Eng Sample," e.g. "AMD Eng Sample, |
| * ZD302046W4K43_36/30/20_2/8_A" "AMD Eng Sample: |
| * 2D3151A2M88E4_35/31_N" |
| */ |
| if (previousState.context_engineering != NULL) { |
| if (memcmp(token_start, "Sample,", token_length) == 0 || |
| memcmp(token_start, "Sample:", token_length) == 0) { |
| state->engineering_sample = true; |
| return false; |
| } |
| } |
| break; |
| case 8: |
| /* Erase "QuadCore" in "VIA QuadCore L4700 @ 1.2+ GHz" |
| */ |
| if (erase_matching(token_start, token_length, "QuadCore")) { |
| state->context_core = token_end; |
| return true; |
| } |
| /* Erase "Six-Core" in "AMD FX(tm)-6100 Six-Core |
| * Processor" */ |
| if (erase_matching(token_start, token_length, "Six-Core")) { |
| state->context_core = token_end; |
| return true; |
| } |
| break; |
| case 9: |
| if (erase_matching(token_start, token_length, "Processor")) { |
| return true; |
| } |
| if (erase_matching(token_start, token_length, "processor")) { |
| return true; |
| } |
| /* Erase "Dual-Core" in "Pentium(R) Dual-Core CPU T4200 |
| * @ 2.00GHz" */ |
| if (erase_matching(token_start, token_length, "Dual-Core")) { |
| state->context_core = token_end; |
| return true; |
| } |
| /* Erase "Quad-Core" in AMD processors, e.g. |
| * "Quad-Core AMD Opteron(tm) Processor 2347 HE" |
| * "AMD FX(tm)-4170 Quad-Core Processor" |
| */ |
| if (erase_matching(token_start, token_length, "Quad-Core")) { |
| state->context_core = token_end; |
| return true; |
| } |
| /* Erase "Transmeta" in brand string on Transmeta |
| * processors, e.g. "Transmeta(tm) Crusoe(tm) Processor |
| * TM5800" "Transmeta Efficeon(tm) Processor TM8000" |
| */ |
| if (erase_matching(token_start, token_length, "Transmeta")) { |
| return true; |
| } |
| break; |
| case 10: |
| /* |
| * Erase "Eight-Core" in AMD processors, e.g. |
| * "AMD FX(tm)-8150 Eight-Core Processor" |
| */ |
| if (erase_matching(token_start, token_length, "Eight-Core")) { |
| state->context_core = token_end; |
| return true; |
| } |
| break; |
| case 11: |
| /* |
| * Erase "Triple-Core" in AMD processors, e.g. |
| * "AMD Phenom(tm) II N830 Triple-Core Processor" |
| * "AMD Phenom(tm) 8650 Triple-Core Processor" |
| */ |
| if (erase_matching(token_start, token_length, "Triple-Core")) { |
| state->context_core = token_end; |
| return true; |
| } |
| /* |
| * Remember to discard string if it contains |
| * "Engineering Sample", e.g. "AMD Engineering Sample" |
| */ |
| if (memcmp(token_start, "Engineering", token_length) == 0) { |
| state->context_engineering = token_start; |
| return true; |
| } |
| break; |
| } |
| if (is_zero_number(token_start, token_end)) { |
| memset(token_start, ' ', token_length); |
| return true; |
| } |
| /* On some Intel processors the last letter of the name is put before |
| * the number, and an additional space it added, e.g. "Intel(R) Core(TM) |
| * i7 CPU X 990 @ 3.47GHz" "Intel(R) Core(TM) CPU Q 820 @ 1.73GHz" |
| * "Intel(R) Core(TM) i5 CPU M 480 @ 2.67GHz" We fix this issue, i.e. |
| * "X 990" -> "990X", "Q 820" |
| * -> "820Q" |
| */ |
| if (previousState.context_upper_letter != 0) { |
| /* A single letter token followed by 2-to-5 digit letter is |
| * merged together |
| */ |
| switch (token_length) { |
| case 2: |
| case 3: |
| case 4: |
| case 5: |
| if (is_number(token_start, token_end)) { |
| /* Load the previous single-letter token |
| */ |
| const char letter = *previousState.context_upper_letter; |
| /* Erase the previous single-letter |
| * token */ |
| *previousState.context_upper_letter = ' '; |
| /* Move the current token one position |
| * to the left */ |
| move_token(token_start, token_end, token_start - 1); |
| token_start -= 1; |
| /* |
| * Add the letter on the end |
| * Note: accessing token_start[-1] is |
| * safe because this is not the first |
| * token |
| */ |
| token_end[-1] = letter; |
| } |
| } |
| } |
| if (state->frequency_separator != NULL) { |
| if (is_model_number(token_start, token_end)) { |
| state->parsed_model_number = true; |
| } |
| } |
| if (is_frequency(token_start, token_end)) { |
| state->frequency_token = true; |
| } |
| return true; |
| } |
| |
| uint32_t cpuinfo_x86_normalize_brand_string(const char raw_name[48], char normalized_name[48]) { |
| normalized_name[0] = '\0'; |
| char name[48]; |
| memcpy(name, raw_name, sizeof(name)); |
| |
| /* |
| * First find the end of the string |
| * Start search from the end because some brand strings contain zeroes |
| * in the middle |
| */ |
| char* name_end = &name[48]; |
| while (name_end[-1] == '\0') { |
| /* |
| * Adject name_end by 1 position and check that we didn't reach |
| * the start of the brand string. This is possible if all |
| * characters are zero. |
| */ |
| if (--name_end == name) { |
| /* All characters are zeros */ |
| return 0; |
| } |
| } |
| |
| struct parser_state parser_state = {0}; |
| |
| /* Now unify all whitespace characters: replace tabs and '\0' with |
| * spaces */ |
| { |
| bool inside_parentheses = false; |
| for (char* char_ptr = name; char_ptr != name_end; char_ptr++) { |
| switch (*char_ptr) { |
| case '(': |
| inside_parentheses = true; |
| *char_ptr = ' '; |
| break; |
| case ')': |
| inside_parentheses = false; |
| *char_ptr = ' '; |
| break; |
| case '@': |
| parser_state.frequency_separator = char_ptr; |
| case '\0': |
| case '\t': |
| *char_ptr = ' '; |
| break; |
| default: |
| if (inside_parentheses) { |
| *char_ptr = ' '; |
| } |
| } |
| } |
| } |
| |
| /* Iterate through all tokens and erase redundant parts */ |
| { |
| bool is_token = false; |
| char* token_start = NULL; |
| for (char* char_ptr = name; char_ptr != name_end; char_ptr++) { |
| if (*char_ptr == ' ') { |
| if (is_token) { |
| is_token = false; |
| if (!transform_token(token_start, char_ptr, &parser_state)) { |
| name_end = char_ptr; |
| break; |
| } |
| } |
| } else { |
| if (!is_token) { |
| is_token = true; |
| token_start = char_ptr; |
| } |
| } |
| } |
| if (is_token) { |
| transform_token(token_start, name_end, &parser_state); |
| } |
| } |
| |
| /* If this is an engineering sample, return empty string */ |
| if (parser_state.engineering_sample) { |
| return 0; |
| } |
| |
| /* Check if there is some string before the frequency separator. */ |
| if (parser_state.frequency_separator != NULL) { |
| if (is_space(name, parser_state.frequency_separator)) { |
| /* If only frequency is available, return empty string |
| */ |
| return 0; |
| } |
| } |
| |
| /* Compact tokens: collapse multiple spacing into one */ |
| { |
| char* output_ptr = normalized_name; |
| char* token_start = NULL; |
| bool is_token = false; |
| bool previous_token_ends_with_dash = true; |
| bool current_token_starts_with_dash = false; |
| uint32_t token_count = 1; |
| for (char* char_ptr = name; char_ptr != name_end; char_ptr++) { |
| const char character = *char_ptr; |
| if (character == ' ') { |
| if (is_token) { |
| is_token = false; |
| if (!current_token_starts_with_dash && !previous_token_ends_with_dash) { |
| token_count += 1; |
| *output_ptr++ = ' '; |
| } |
| output_ptr = move_token(token_start, char_ptr, output_ptr); |
| /* Note: char_ptr[-1] exists because |
| * there is a token before this space */ |
| previous_token_ends_with_dash = (char_ptr[-1] == '-'); |
| } |
| } else { |
| if (!is_token) { |
| is_token = true; |
| token_start = char_ptr; |
| current_token_starts_with_dash = (character == '-'); |
| } |
| } |
| } |
| if (is_token) { |
| if (!current_token_starts_with_dash && !previous_token_ends_with_dash) { |
| token_count += 1; |
| *output_ptr++ = ' '; |
| } |
| output_ptr = move_token(token_start, name_end, output_ptr); |
| } |
| if (parser_state.frequency_token && token_count <= 1) { |
| /* The only remaining part is frequency */ |
| normalized_name[0] = '\0'; |
| return 0; |
| } |
| if (output_ptr < &normalized_name[48]) { |
| *output_ptr = '\0'; |
| } else { |
| normalized_name[47] = '\0'; |
| } |
| return (uint32_t)(output_ptr - normalized_name); |
| } |
| } |
| |
| static const char* vendor_string_map[] = { |
| [cpuinfo_vendor_intel] = "Intel", |
| [cpuinfo_vendor_amd] = "AMD", |
| [cpuinfo_vendor_via] = "VIA", |
| [cpuinfo_vendor_hygon] = "Hygon", |
| [cpuinfo_vendor_rdc] = "RDC", |
| [cpuinfo_vendor_dmp] = "DM&P", |
| [cpuinfo_vendor_transmeta] = "Transmeta", |
| [cpuinfo_vendor_cyrix] = "Cyrix", |
| [cpuinfo_vendor_rise] = "Rise", |
| [cpuinfo_vendor_nsc] = "NSC", |
| [cpuinfo_vendor_sis] = "SiS", |
| [cpuinfo_vendor_nexgen] = "NexGen", |
| [cpuinfo_vendor_umc] = "UMC", |
| }; |
| |
| uint32_t cpuinfo_x86_format_package_name( |
| enum cpuinfo_vendor vendor, |
| const char normalized_brand_string[48], |
| char package_name[CPUINFO_PACKAGE_NAME_MAX]) { |
| if (normalized_brand_string[0] == '\0') { |
| package_name[0] = '\0'; |
| return 0; |
| } |
| |
| const char* vendor_string = NULL; |
| if ((uint32_t)vendor < (uint32_t)CPUINFO_COUNT_OF(vendor_string_map)) { |
| vendor_string = vendor_string_map[(uint32_t)vendor]; |
| } |
| if (vendor_string == NULL) { |
| strncpy(package_name, normalized_brand_string, CPUINFO_PACKAGE_NAME_MAX); |
| package_name[CPUINFO_PACKAGE_NAME_MAX - 1] = '\0'; |
| return 0; |
| } else { |
| snprintf(package_name, CPUINFO_PACKAGE_NAME_MAX, "%s %s", vendor_string, normalized_brand_string); |
| return (uint32_t)strlen(vendor_string) + 1; |
| } |
| } |