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