arm_compute v18.05
[platform/upstream/armcl.git] / src / runtime / CPUUtils.cpp
1 /*
2  * Copyright (c) 2018 ARM Limited.
3  *
4  * SPDX-License-Identifier: MIT
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 #include "arm_compute/runtime/CPUUtils.h"
25
26 #include "arm_compute/core/CPP/CPPTypes.h"
27 #include "arm_compute/core/Error.h"
28 #include "support/ToolchainSupport.h"
29
30 #include <array>
31 #include <cstdlib>
32 #include <cstring>
33 #include <fcntl.h>
34 #include <fstream>
35 #include <map>
36 #include <sched.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <unistd.h>
40
41 #ifndef BARE_METAL
42 #include <regex>
43 #include <thread>
44 #endif /* BARE_METAL */
45
46 #if !defined(BARE_METAL) && (defined(__arm__) || defined(__aarch64__))
47 #include <sys/auxv.h>
48
49 /* Get HWCAP bits from asm/hwcap.h */
50 #include <asm/hwcap.h>
51 #endif /* !BARE_METAL */
52
53 /* Make sure the bits we care about are defined, just in case asm/hwcap.h is
54  * out of date (or for bare metal mode) */
55 #ifndef HWCAP_ASIMDHP
56 #define HWCAP_ASIMDHP (1 << 10)
57 #endif /* HWCAP_ASIMDHP */
58
59 #ifndef HWCAP_CPUID
60 #define HWCAP_CPUID (1 << 11)
61 #endif /* HWCAP_CPUID */
62
63 #ifndef HWCAP_ASIMDDP
64 #define HWCAP_ASIMDDP (1 << 20)
65 #endif /* HWCAP_ASIMDDP */
66
67 namespace
68 {
69 using namespace arm_compute;
70
71 #if !defined(BARE_METAL) && (defined(__arm__) || defined(__aarch64__))
72 struct PerCPUData
73 {
74     CPUModel     model     = CPUModel::GENERIC;
75     unsigned int midr      = 0;
76     bool         model_set = false;
77 };
78
79 /* Convert an MIDR register value to a CPUModel enum value. */
80 CPUModel midr_to_model(const unsigned int midr)
81 {
82     CPUModel model;
83
84     // Unpack variant and CPU ID
85     const int variant = (midr >> 20) & 0xF;
86     const int cpunum  = (midr >> 4) & 0xFFF;
87
88     // Only CPUs we have code paths for are detected.  All other CPUs can be safely classed as "GENERIC"
89     switch(cpunum)
90     {
91         case 0xd03:
92             model = CPUModel::A53;
93             break;
94
95         case 0xd05:
96             if(variant != 0)
97             {
98                 model = CPUModel::A55r1;
99             }
100             else
101             {
102                 model = CPUModel::A55r0;
103             }
104             break;
105
106         default:
107             model = CPUModel::GENERIC;
108             break;
109     }
110
111     return model;
112 }
113
114 void populate_models_cpuid(std::vector<PerCPUData> &cpusv)
115 {
116     // If the CPUID capability is present, MIDR information is provided in /sys. Use that to populate the CPU model table.
117     uint32_t i = 0;
118     for(auto &c : cpusv)
119     {
120         std::stringstream str;
121         str << "/sys/devices/system/cpu/cpu" << i++ << "/regs/identification/midr_el1";
122         std::ifstream file;
123         file.open(str.str(), std::ios::in);
124         if(file.is_open())
125         {
126             std::string line;
127             if(bool(getline(file, line)))
128             {
129                 const unsigned long midr = support::cpp11::stoul(line, nullptr, 16);
130                 c.midr                   = (midr & 0xffffffff);
131                 c.model                  = midr_to_model(c.midr);
132                 c.model_set              = true;
133             }
134         }
135     }
136 }
137
138 void populate_models_cpuinfo(std::vector<PerCPUData> &cpusv)
139 {
140     // If "long-form" cpuinfo is present, parse that to populate models.
141     std::regex proc_regex("^processor.*(\\d+)$");
142     std::regex imp_regex("^CPU implementer.*0x(..)$");
143     std::regex var_regex("^CPU variant.*0x(.)$");
144     std::regex part_regex("^CPU part.*0x(...)$");
145     std::regex rev_regex("^CPU revision.*(\\d+)$");
146
147     std::ifstream file;
148     file.open("/proc/cpuinfo", std::ios::in);
149
150     if(file.is_open())
151     {
152         std::string line;
153         int         midr   = 0;
154         int         curcpu = -1;
155
156         while(bool(getline(file, line)))
157         {
158             std::smatch match;
159
160             if(std::regex_match(line, match, proc_regex))
161             {
162                 std::string id     = match[1];
163                 int         newcpu = support::cpp11::stoi(id, nullptr, 0);
164
165                 if(curcpu >= 0 && midr == 0)
166                 {
167                     // Matched a new CPU ID without any description of the previous one - looks like old format.
168                     return;
169                 }
170
171                 if(curcpu >= 0)
172                 {
173                     cpusv[curcpu].midr      = midr;
174                     cpusv[curcpu].model     = midr_to_model(midr);
175                     cpusv[curcpu].model_set = true;
176                 }
177
178                 midr   = 0;
179                 curcpu = newcpu;
180
181                 continue;
182             }
183
184             if(std::regex_match(line, match, imp_regex))
185             {
186                 int impv = support::cpp11::stoi(match[1], nullptr, 16);
187                 midr |= (impv << 24);
188                 continue;
189             }
190
191             if(std::regex_match(line, match, var_regex))
192             {
193                 int varv = support::cpp11::stoi(match[1], nullptr, 16);
194                 midr |= (varv << 16);
195                 continue;
196             }
197
198             if(std::regex_match(line, match, part_regex))
199             {
200                 int partv = support::cpp11::stoi(match[1], nullptr, 16);
201                 midr |= (partv << 4);
202                 continue;
203             }
204
205             if(std::regex_match(line, match, rev_regex))
206             {
207                 int regv = support::cpp11::stoi(match[1], nullptr, 10);
208                 midr |= (regv);
209                 midr |= (0xf << 16);
210                 continue;
211             }
212         }
213
214         if(curcpu >= 0)
215         {
216             cpusv[curcpu].midr      = midr;
217             cpusv[curcpu].model     = midr_to_model(midr);
218             cpusv[curcpu].model_set = true;
219         }
220     }
221 }
222
223 int get_max_cpus()
224 {
225     int max_cpus = 1;
226 #if !defined(BARE_METAL) && (defined(__arm__) || defined(__aarch64__))
227     std::ifstream CPUspresent;
228     CPUspresent.open("/sys/devices/system/cpu/present", std::ios::in);
229     bool success = false;
230
231     if(CPUspresent.is_open())
232     {
233         std::string line;
234
235         if(bool(getline(CPUspresent, line)))
236         {
237             /* The content of this file is a list of ranges or single values, e.g.
238                  * 0-5, or 1-3,5,7 or similar.  As we are interested in the
239                  * max valid ID, we just need to find the last valid
240                  * delimiter ('-' or ',') and parse the integer immediately after that.
241                  */
242             auto startfrom = line.begin();
243
244             for(auto i = line.begin(); i < line.end(); ++i)
245             {
246                 if(*i == '-' || *i == ',')
247                 {
248                     startfrom = i + 1;
249                 }
250             }
251
252             line.erase(line.begin(), startfrom);
253
254             max_cpus = support::cpp11::stoi(line, nullptr, 0) + 1;
255             success  = true;
256         }
257     }
258
259     // Return std::thread::hardware_concurrency() as a fallback.
260     if(!success)
261     {
262         max_cpus = std::thread::hardware_concurrency();
263     }
264 #endif /* BARE_METAL */
265
266     return max_cpus;
267 }
268 #endif /* !defined(BARE_METAL) && (defined(__arm__) || defined(__aarch64__)) */
269
270 } // namespace
271
272 namespace arm_compute
273 {
274 void get_cpu_configuration(CPUInfo &cpuinfo)
275 {
276 #if !defined(BARE_METAL) && (defined(__arm__) || defined(__aarch64__))
277     bool cpuid        = false;
278     bool fp16_support = false;
279     bool dot_support  = false;
280
281     const uint32_t hwcaps = getauxval(AT_HWCAP);
282
283     if((hwcaps & HWCAP_CPUID) != 0)
284     {
285         cpuid = true;
286     }
287
288     if((hwcaps & HWCAP_ASIMDHP) != 0)
289     {
290         fp16_support = true;
291     }
292
293     if((hwcaps & HWCAP_ASIMDDP) != 0)
294     {
295         dot_support = true;
296     }
297
298 #ifdef __aarch64__
299     /* Pre-4.15 kernels don't have the ASIMDDP bit.
300      *
301      * Although the CPUID bit allows us to read the feature register
302      * directly, the kernel quite sensibly masks this to only show
303      * features known by it to be safe to show to userspace.  As a
304      * result, pre-4.15 kernels won't show the relevant bit in the
305      * feature registers either.
306      *
307      * So for now, use a whitelist of CPUs known to support the feature.
308      */
309     if(!dot_support && cpuid)
310     {
311         /* List of CPUs with dot product support:         A55r1       A75r1       A75r2  */
312         const unsigned int dotprod_whitelist_masks[]  = { 0xfff0fff0, 0xfff0fff0, 0xfff0fff0, 0 };
313         const unsigned int dotprod_whitelist_values[] = { 0x4110d050, 0x4110d0a0, 0x4120d0a0, 0 };
314
315         unsigned long cpuid;
316
317         __asm __volatile(
318             "mrs %0, midr_el1\n"
319             : "=r"(cpuid)
320             :
321             : );
322
323         for(int i = 0; dotprod_whitelist_values[i] != 0; i++)
324         {
325             if((cpuid & dotprod_whitelist_masks[i]) == dotprod_whitelist_values[i])
326             {
327                 dot_support = true;
328                 break;
329             }
330         }
331     }
332 #endif /* __aarch64__ */
333     const unsigned int max_cpus = get_max_cpus();
334     cpuinfo.set_cpu_num(max_cpus);
335     cpuinfo.set_fp16(fp16_support);
336     cpuinfo.set_dotprod(dot_support);
337     std::vector<PerCPUData> percpu(max_cpus);
338     if(cpuid)
339     {
340         populate_models_cpuid(percpu);
341     }
342     else
343     {
344         populate_models_cpuinfo(percpu);
345     }
346     int j(0);
347     for(const auto &v : percpu)
348     {
349         cpuinfo.set_cpu_model(j++, v.model);
350     }
351 #else  /* !defined(BARE_METAL) && (defined(__arm__) || defined(__aarch64__)) */
352     ARM_COMPUTE_UNUSED(cpuinfo);
353 #endif /* !defined(BARE_METAL) && (defined(__arm__) || defined(__aarch64__)) */
354 }
355
356 unsigned int get_threads_hint()
357 {
358     unsigned int num_threads_hint = 1;
359
360 #ifndef BARE_METAL
361     std::map<std::string, unsigned int> cpu_part_occurrence_map;
362
363     // CPU part regex
364     std::regex  cpu_part_rgx(R"(.*CPU part.+?(?=:).+?(?=\w+)(\w+).*)");
365     std::smatch cpu_part_match;
366
367     // Read cpuinfo and get occurrence of each core
368     std::ifstream cpuinfo;
369     cpuinfo.open("/proc/cpuinfo", std::ios::in);
370     if(cpuinfo.is_open())
371     {
372         std::string line;
373         while(bool(getline(cpuinfo, line)))
374         {
375             if(std::regex_search(line.cbegin(), line.cend(), cpu_part_match, cpu_part_rgx))
376             {
377                 std::string cpu_part = cpu_part_match[1];
378                 if(cpu_part_occurrence_map.find(cpu_part) != cpu_part_occurrence_map.end())
379                 {
380                     cpu_part_occurrence_map[cpu_part]++;
381                 }
382                 else
383                 {
384                     cpu_part_occurrence_map[cpu_part] = 1;
385                 }
386             }
387         }
388     }
389
390     // Get min number of threads
391     auto min_common_cores = std::min_element(cpu_part_occurrence_map.begin(), cpu_part_occurrence_map.end(),
392                                              [](const std::pair<std::string, unsigned int> &p1, const std::pair<std::string, unsigned int> &p2)
393     {
394         return p1.second < p2.second;
395     });
396
397     // Set thread hint
398     num_threads_hint = cpu_part_occurrence_map.empty() ? std::thread::hardware_concurrency() : min_common_cores->second;
399 #endif /* BARE_METAL */
400
401     return num_threads_hint;
402 }
403
404 } // namespace arm_compute