1/* 2 * Copyright 2016, General Dynamics C4 Systems 3 * 4 * SPDX-License-Identifier: GPL-2.0-only 5 */ 6 7#include <string.h> 8#include <util.h> 9#include <arch/machine.h> 10 11/** @file Support routines for identifying the processor family, model, etc 12 * on INTEL x86 processors, as well as attempting to determine the model string. 13 * 14 * AMD processors would be different. 15 */ 16 17const char X86_CPUID_VENDOR_STRING_INTEL[] = {'G', 'e', 'n', 'u', 'i', 'n', 'e', 'I', 'n', 't', 'e', 'l', 0}; 18const char X86_CPUID_VENDOR_STRING_AMD_LEGACY[] = { 'A', 'M', 'D', 'i', 's', 'b', 'e', 't', 't', 'e', 'r', '!', 0}; 19const char X86_CPUID_VENDOR_STRING_AMD[] = {'A', 'u', 't', 'h', 'e', 'n', 't', 'i', 'c', 'A', 'M', 'D', 0}; 20 21BOOT_BSS static cpu_identity_t cpu_identity; 22 23BOOT_CODE cpu_identity_t *x86_cpuid_get_identity(void) 24{ 25 return &cpu_identity; 26} 27 28BOOT_CODE x86_cpu_identity_t *x86_cpuid_get_model_info(void) 29{ 30 return &cpu_identity.display; 31} 32 33/** Extracts the vendor string from CPUID_000H.E[BCD]X. 34 * Will be one of "GenuineIntel", "AMDisbetter!", "AuthenticAMD", "CentaurHauls" 35 * etc. We don't support x86 CPUs from vendors other than AMD and Intel. 36 */ 37BOOT_CODE static void x86_cpuid_fill_vendor_string(cpu_identity_t *ci) 38{ 39 MAY_ALIAS uint32_t *vendor_string32 = (uint32_t *)ci->vendor_string; 40 41 if (ci == NULL) { 42 return; 43 } 44 45 vendor_string32[0] = x86_cpuid_ebx(0, 0); 46 vendor_string32[1] = x86_cpuid_edx(0, 0); 47 vendor_string32[2] = x86_cpuid_ecx(0, 0); 48 49 ci->vendor_string[X86_CPUID_VENDOR_STRING_MAXLENGTH] = '\0'; 50} 51 52struct family_model { 53 uint8_t family, model; 54}; 55 56BOOT_CODE static void x86_cpuid_intel_identity_initialize(cpu_identity_t *ci, 57 struct family_model original) 58{ 59 /* Next, there are some values which require additional adjustment, and 60 * require you to take into account an additional extended family and model 61 * ID. 62 * 63 * See Intel manuals vol2, section 3.2 for the literal constants. 64 */ 65 if (original.family != 0x0F) { 66 ci->display.family = original.family; 67 } else { 68 ci->display.family = ci->display.extended_family + original.family; 69 } 70 71 /* The Intel manuals' wording would make you think you should use the 72 * original family_ID value read from CPUID.EAX, like: 73 * if (original->family == 0x06 || original->family == 0x0F) { 74 * 75 * But Linux doesn't do that, Linux uses the family_ID value AFTER 76 * adjustment, like: 77 * if (ci->display.family == 0x06 || ci->display.family == 0x0F) { 78 * 79 * Additionally, even though the Intel manuals say to adjust the model 80 * number if the family number is 0x6 OR 0xF, Linux just adusts it as long 81 * as the family number is GREATER THAN OR EQUAL to 0x6. 82 * 83 * I have followed Linux in the first case, where it could be a case of 84 * them having the correct interpretation of the text, but in the second case 85 * where they flagrantly disobey the manual, I have not followed them. 86 * 87 * See Linux source at: /arch/x86/lib/cpu.c: 88 * http://lxr.free-electrons.com/source/arch/x86/lib/cpu.c 89 */ 90 if (ci->display.family == 0x06 || ci->display.family == 0x0F) { 91 ci->display.model = (ci->display.extended_model << 4u) + original.model; 92 } else { 93 ci->display.model = original.model; 94 } 95} 96 97BOOT_CODE static void x86_cpuid_amd_identity_initialize(cpu_identity_t *ci, 98 struct family_model original) 99{ 100 /* Intel and AMD's specifications give slightly different ways to compose 101 * the family and model IDs (AMD CPUID manual, section 2.) 102 * 103 * AMD says that if family is LESS THAN 0xF, then adjustments are needed. 104 * Intel says that if family == 0xF || family == 0x6, then adjustments are 105 * needed. 106 */ 107 if (original.family < 0xF) { 108 ci->display.family = original.family; 109 ci->display.model = original.model; 110 } else { 111 ci->display.family = original.family + ci->display.extended_family; 112 ci->display.family = (ci->display.extended_model << 4u) + original.model; 113 } 114} 115 116bool_t x86_cpuid_initialize(void) 117{ 118 cpu_identity_t *ci = x86_cpuid_get_identity(); 119 struct family_model original; 120 cpuid_001h_eax_t eax; 121 cpuid_001h_ebx_t ebx; 122 123 memset(ci, 0, sizeof(*ci)); 124 125 /* First determine which vendor manufactured the CPU. */ 126 x86_cpuid_fill_vendor_string(ci); 127 128 /* Need both eax and ebx ouput values. */ 129 eax.words[0] = x86_cpuid_eax(1, 0); 130 ebx.words[0] = x86_cpuid_ebx(1, 0); 131 132 /* We now use EAX for the family, model, stepping values, and EBX for the 133 * brand index. Store the original values from CPUID_001H.EAX. 134 */ 135 original.family = cpuid_001h_eax_get_family(eax); 136 original.model = cpuid_001h_eax_get_model(eax); 137 ci->display.stepping = cpuid_001h_eax_get_stepping(eax); 138 139 /* Also store extended family and model values used for adjustment */ 140 ci->display.extended_family = cpuid_001h_eax_get_extended_family(eax); 141 ci->display.extended_model = cpuid_001h_eax_get_extended_model(eax); 142 143 /* Also store the brand index value given in EBX */ 144 ci->display.brand = cpuid_001h_ebx_get_brand(ebx); 145 146 if (strncmp(ci->vendor_string, X86_CPUID_VENDOR_STRING_INTEL, 147 X86_CPUID_VENDOR_STRING_MAXLENGTH) == 0) { 148 ci->vendor = X86_VENDOR_INTEL; 149 x86_cpuid_intel_identity_initialize(ci, original); 150 return true; 151 } else if (strncmp(ci->vendor_string, X86_CPUID_VENDOR_STRING_AMD_LEGACY, 152 X86_CPUID_VENDOR_STRING_MAXLENGTH) == 0 153 || strncmp(ci->vendor_string, X86_CPUID_VENDOR_STRING_AMD, 154 X86_CPUID_VENDOR_STRING_MAXLENGTH) == 0) { 155 ci->vendor = X86_VENDOR_AMD; 156 x86_cpuid_amd_identity_initialize(ci, original); 157 return true; 158 } else { 159 /* CPU from unsupported vendor. Examples could be Cyrix, Centaur, etc. 160 * The old time x86 clones. Return false to the boot and let the upper 161 * level caller decide what to do. 162 */ 163 ci->vendor = X86_VENDOR_OTHER; 164 return false; 165 } 166} 167