1 // Copyright 2022 The Abseil Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "absl/crc/internal/cpu_detect.h"
16 
17 #include <cstdint>
18 #include <string>
19 
20 #include "absl/base/config.h"
21 
22 #if defined(__aarch64__) && defined(__linux__)
23 #include <asm/hwcap.h>
24 #include <sys/auxv.h>
25 #endif
26 
27 #if defined(_WIN32) || defined(_WIN64)
28 #include <intrin.h>
29 #endif
30 
31 namespace absl {
32 ABSL_NAMESPACE_BEGIN
33 namespace crc_internal {
34 
35 #if defined(__x86_64__) || defined(_M_X64)
36 
37 namespace {
38 
39 #if !defined(_WIN32) && !defined(_WIN64)
40 // MSVC defines this function for us.
41 // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
__cpuid(int cpu_info[4],int info_type)42 static void __cpuid(int cpu_info[4], int info_type) {
43   __asm__ volatile("cpuid \n\t"
44                    : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]),
45                      "=d"(cpu_info[3])
46                    : "a"(info_type), "c"(0));
47 }
48 #endif  // !defined(_WIN32) && !defined(_WIN64)
49 
50 enum class Vendor {
51   kUnknown,
52   kIntel,
53   kAmd,
54 };
55 
GetVendor()56 Vendor GetVendor() {
57   // Get the vendor string (issue CPUID with eax = 0).
58   int cpu_info[4];
59   __cpuid(cpu_info, 0);
60 
61   std::string vendor;
62   vendor.append(reinterpret_cast<char*>(&cpu_info[1]), 4);
63   vendor.append(reinterpret_cast<char*>(&cpu_info[3]), 4);
64   vendor.append(reinterpret_cast<char*>(&cpu_info[2]), 4);
65   if (vendor == "GenuineIntel") {
66     return Vendor::kIntel;
67   } else if (vendor == "AuthenticAMD") {
68     return Vendor::kAmd;
69   } else {
70     return Vendor::kUnknown;
71   }
72 }
73 
GetIntelCpuType()74 CpuType GetIntelCpuType() {
75   // To get general information and extended features we send eax = 1 and
76   // ecx = 0 to cpuid.  The response is returned in eax, ebx, ecx and edx.
77   // (See Intel 64 and IA-32 Architectures Software Developer's Manual
78   // Volume 2A: Instruction Set Reference, A-M CPUID).
79   // https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-2a-manual.html
80   // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
81   int cpu_info[4];
82   __cpuid(cpu_info, 1);
83 
84   // Response in eax bits as follows:
85   // 0-3 (stepping id)
86   // 4-7 (model number),
87   // 8-11 (family code),
88   // 12-13 (processor type),
89   // 16-19 (extended model)
90   // 20-27 (extended family)
91 
92   int family = (cpu_info[0] >> 8) & 0x0f;
93   int model_num = (cpu_info[0] >> 4) & 0x0f;
94   int ext_family = (cpu_info[0] >> 20) & 0xff;
95   int ext_model_num = (cpu_info[0] >> 16) & 0x0f;
96 
97   int brand_id = cpu_info[1] & 0xff;
98 
99   // Process the extended family and model info if necessary
100   if (family == 0x0f) {
101     family += ext_family;
102   }
103 
104   if (family == 0x0f || family == 0x6) {
105     model_num += (ext_model_num << 4);
106   }
107 
108   switch (brand_id) {
109     case 0:  // no brand ID, so parse CPU family/model
110       switch (family) {
111         case 6:  // Most PentiumIII processors are in this category
112           switch (model_num) {
113             case 0x2c:  // Westmere: Gulftown
114               return CpuType::kIntelWestmere;
115             case 0x2d:  // Sandybridge
116               return CpuType::kIntelSandybridge;
117             case 0x3e:  // Ivybridge
118               return CpuType::kIntelIvybridge;
119             case 0x3c:  // Haswell (client)
120             case 0x3f:  // Haswell
121               return CpuType::kIntelHaswell;
122             case 0x4f:  // Broadwell
123             case 0x56:  // BroadwellDE
124               return CpuType::kIntelBroadwell;
125             case 0x55:                 // Skylake Xeon
126               if ((cpu_info[0] & 0x0f) < 5) {  // stepping < 5 is skylake
127                 return CpuType::kIntelSkylakeXeon;
128               } else {  // stepping >= 5 is cascadelake
129                 return CpuType::kIntelCascadelakeXeon;
130               }
131             case 0x5e:  // Skylake (client)
132               return CpuType::kIntelSkylake;
133             default:
134               return CpuType::kUnknown;
135           }
136         default:
137           return CpuType::kUnknown;
138       }
139     default:
140       return CpuType::kUnknown;
141   }
142 }
143 
GetAmdCpuType()144 CpuType GetAmdCpuType() {
145   // To get general information and extended features we send eax = 1 and
146   // ecx = 0 to cpuid.  The response is returned in eax, ebx, ecx and edx.
147   // (See Intel 64 and IA-32 Architectures Software Developer's Manual
148   // Volume 2A: Instruction Set Reference, A-M CPUID).
149   // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
150   int cpu_info[4];
151   __cpuid(cpu_info, 1);
152 
153   // Response in eax bits as follows:
154   // 0-3 (stepping id)
155   // 4-7 (model number),
156   // 8-11 (family code),
157   // 12-13 (processor type),
158   // 16-19 (extended model)
159   // 20-27 (extended family)
160 
161   int family = (cpu_info[0] >> 8) & 0x0f;
162   int model_num = (cpu_info[0] >> 4) & 0x0f;
163   int ext_family = (cpu_info[0] >> 20) & 0xff;
164   int ext_model_num = (cpu_info[0] >> 16) & 0x0f;
165 
166   if (family == 0x0f) {
167     family += ext_family;
168     model_num += (ext_model_num << 4);
169   }
170 
171   switch (family) {
172     case 0x17:
173       switch (model_num) {
174         case 0x0:  // Stepping Ax
175         case 0x1:  // Stepping Bx
176           return CpuType::kAmdNaples;
177         case 0x30:  // Stepping Ax
178         case 0x31:  // Stepping Bx
179           return CpuType::kAmdRome;
180         default:
181           return CpuType::kUnknown;
182       }
183       break;
184     case 0x19:
185       switch (model_num) {
186         case 0x1:  // Stepping B0
187           return CpuType::kAmdMilan;
188         default:
189           return CpuType::kUnknown;
190       }
191       break;
192     default:
193       return CpuType::kUnknown;
194   }
195 }
196 
197 }  // namespace
198 
GetCpuType()199 CpuType GetCpuType() {
200   switch (GetVendor()) {
201     case Vendor::kIntel:
202       return GetIntelCpuType();
203     case Vendor::kAmd:
204       return GetAmdCpuType();
205     default:
206       return CpuType::kUnknown;
207   }
208 }
209 
SupportsArmCRC32PMULL()210 bool SupportsArmCRC32PMULL() { return false; }
211 
212 #elif defined(__aarch64__) && defined(__linux__)
213 
214 #ifndef HWCAP_CPUID
215 #define HWCAP_CPUID (1 << 11)
216 #endif
217 
218 #define ABSL_INTERNAL_AARCH64_ID_REG_READ(id, val) \
219   asm("mrs %0, " #id : "=r"(val))
220 
221 CpuType GetCpuType() {
222   // MIDR_EL1 is not visible to EL0, however the access will be emulated by
223   // linux if AT_HWCAP has HWCAP_CPUID set.
224   //
225   // This method will be unreliable on heterogeneous computing systems (ex:
226   // big.LITTLE) since the value of MIDR_EL1 will change based on the calling
227   // thread.
228   uint64_t hwcaps = getauxval(AT_HWCAP);
229   if (hwcaps & HWCAP_CPUID) {
230     uint64_t midr = 0;
231     ABSL_INTERNAL_AARCH64_ID_REG_READ(MIDR_EL1, midr);
232     uint32_t implementer = (midr >> 24) & 0xff;
233     uint32_t part_number = (midr >> 4) & 0xfff;
234     if (implementer == 0x41 && part_number == 0xd0c) {
235       return CpuType::kArmNeoverseN1;
236     }
237   }
238   return CpuType::kUnknown;
239 }
240 
241 bool SupportsArmCRC32PMULL() {
242   uint64_t hwcaps = getauxval(AT_HWCAP);
243   return (hwcaps & HWCAP_CRC32) && (hwcaps & HWCAP_PMULL);
244 }
245 
246 #else
247 
248 CpuType GetCpuType() { return CpuType::kUnknown; }
249 
250 bool SupportsArmCRC32PMULL() { return false; }
251 
252 #endif
253 
254 }  // namespace crc_internal
255 ABSL_NAMESPACE_END
256 }  // namespace absl
257