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 
17 const char X86_CPUID_VENDOR_STRING_INTEL[] = {'G', 'e', 'n', 'u', 'i', 'n', 'e', 'I', 'n', 't', 'e', 'l', 0};
18 const char X86_CPUID_VENDOR_STRING_AMD_LEGACY[] = { 'A', 'M', 'D', 'i', 's', 'b', 'e', 't', 't', 'e', 'r', '!', 0};
19 const char X86_CPUID_VENDOR_STRING_AMD[] = {'A', 'u', 't', 'h', 'e', 'n', 't', 'i', 'c', 'A', 'M', 'D', 0};
20 
21 BOOT_BSS static cpu_identity_t cpu_identity;
22 
x86_cpuid_get_identity(void)23 BOOT_CODE cpu_identity_t *x86_cpuid_get_identity(void)
24 {
25     return &cpu_identity;
26 }
27 
x86_cpuid_get_model_info(void)28 BOOT_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  */
x86_cpuid_fill_vendor_string(cpu_identity_t * ci)37 BOOT_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 
52 struct family_model {
53     uint8_t family, model;
54 };
55 
x86_cpuid_intel_identity_initialize(cpu_identity_t * ci,struct family_model original)56 BOOT_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 
x86_cpuid_amd_identity_initialize(cpu_identity_t * ci,struct family_model original)97 BOOT_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 
x86_cpuid_initialize(void)116 bool_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