cpu-features.c revision 4b2196c929b70f2cdc1c2556580d349db89356d8
1/*
2 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include <sys/system_properties.h>
12#ifdef __arm__
13#include <machine/cpu-features.h>
14#endif
15#include <pthread.h>
16#include "cpu-features.h"
17#include <stdio.h>
18#include <stdlib.h>
19#include <fcntl.h>
20#include <errno.h>
21
22static  pthread_once_t     g_once;
23static  AndroidCpuFamily   g_cpuFamily;
24static  uint64_t           g_cpuFeatures;
25static  int                g_cpuCount;
26
27static const int  android_cpufeatures_debug = 0;
28
29#ifdef __arm__
30#  define DEFAULT_CPU_FAMILY  ANDROID_CPU_FAMILY_ARM
31#elif defined __i386__
32#  define DEFAULT_CPU_FAMILY  ANDROID_CPU_FAMILY_X86
33#else
34#  define DEFAULT_CPU_FAMILY  ANDROID_CPU_FAMILY_UNKNOWN
35#endif
36
37#define  D(...) \
38    do { \
39        if (android_cpufeatures_debug) { \
40            printf(__VA_ARGS__); fflush(stdout); \
41        } \
42    } while (0)
43
44#ifdef __i386__
45static __inline__ void x86_cpuid(int func, int values[4])
46{
47    int a, b, c, d;
48    /* We need to preserve ebx since we're compiling PIC code */
49    /* this means we can't use "=b" for the second output register */
50    __asm__ __volatile__ ( \
51      "push %%ebx\n"
52      "cpuid\n" \
53      "mov %1, %%ebx\n"
54      "pop %%ebx\n"
55      : "=a" (a), "=r" (b), "=c" (c), "=d" (d) \
56      : "a" (func) \
57    );
58    values[0] = a;
59    values[1] = b;
60    values[2] = c;
61    values[3] = d;
62}
63#endif
64
65/* Read the content of /proc/cpuinfo into a user-provided buffer.
66 * Return the length of the data, or -1 on error. Does *not*
67 * zero-terminate the content. Will not read more
68 * than 'buffsize' bytes.
69 */
70static int
71read_file(const char*  pathname, char*  buffer, size_t  buffsize)
72{
73    int  fd, len;
74
75    fd = open(pathname, O_RDONLY);
76    if (fd < 0)
77        return -1;
78
79    do {
80        len = read(fd, buffer, buffsize);
81    } while (len < 0 && errno == EINTR);
82
83    close(fd);
84
85    return len;
86}
87
88/* Extract the content of a the first occurence of a given field in
89 * the content of /proc/cpuinfo and return it as a heap-allocated
90 * string that must be freed by the caller.
91 *
92 * Return NULL if not found
93 */
94static char*
95extract_cpuinfo_field(char* buffer, int buflen, const char* field)
96{
97    int  fieldlen = strlen(field);
98    char* bufend = buffer + buflen;
99    char* result = NULL;
100    int len, ignore;
101    const char *p, *q;
102
103    /* Look for first field occurence, and ensures it starts the line.
104     */
105    p = buffer;
106    bufend = buffer + buflen;
107    for (;;) {
108        p = memmem(p, bufend-p, field, fieldlen);
109        if (p == NULL)
110            goto EXIT;
111
112        if (p == buffer || p[-1] == '\n')
113            break;
114
115        p += fieldlen;
116    }
117
118    /* Skip to the first column followed by a space */
119    p += fieldlen;
120    p  = memchr(p, ':', bufend-p);
121    if (p == NULL || p[1] != ' ')
122        goto EXIT;
123
124    /* Find the end of the line */
125    p += 2;
126    q = memchr(p, '\n', bufend-p);
127    if (q == NULL)
128        q = bufend;
129
130    /* Copy the line into a heap-allocated buffer */
131    len = q-p;
132    result = malloc(len+1);
133    if (result == NULL)
134        goto EXIT;
135
136    memcpy(result, p, len);
137    result[len] = '\0';
138
139EXIT:
140    return result;
141}
142
143/* Count the number of occurences of a given field prefix in /proc/cpuinfo.
144 */
145static int
146count_cpuinfo_field(char* buffer, int buflen, const char* field)
147{
148    int fieldlen = strlen(field);
149    const char* p = buffer;
150    const char* bufend = buffer + buflen;
151    const char* q;
152    int count = 0;
153
154    for (;;) {
155        const char* q;
156
157        p = memmem(p, bufend-p, field, fieldlen);
158        if (p == NULL)
159            break;
160
161        /* Ensure that the field is at the start of a line */
162        if (p > buffer && p[-1] != '\n') {
163            p += fieldlen;
164            continue;
165        }
166
167
168        /* skip any whitespace */
169        q = p + fieldlen;
170        while (q < bufend && (*q == ' ' || *q == '\t'))
171            q++;
172
173        /* we must have a colon now */
174        if (q < bufend && *q == ':') {
175            count += 1;
176            q ++;
177        }
178        p = q;
179    }
180
181    return count;
182}
183
184/* Like strlen(), but for constant string literals */
185#define STRLEN_CONST(x)  ((sizeof(x)-1)
186
187
188/* Checks that a space-separated list of items contains one given 'item'.
189 * Returns 1 if found, 0 otherwise.
190 */
191static int
192has_list_item(const char* list, const char* item)
193{
194    const char*  p = list;
195    int itemlen = strlen(item);
196
197    if (list == NULL)
198        return 0;
199
200    while (*p) {
201        const char*  q;
202
203        /* skip spaces */
204        while (*p == ' ' || *p == '\t')
205            p++;
206
207        /* find end of current list item */
208        q = p;
209        while (*q && *q != ' ' && *q != '\t')
210            q++;
211
212        if (itemlen == q-p && !memcmp(p, item, itemlen))
213            return 1;
214
215        /* skip to next item */
216        p = q;
217    }
218    return 0;
219}
220
221
222static void
223android_cpuInit(void)
224{
225    char cpuinfo[4096];
226    int  cpuinfo_len;
227
228    g_cpuFamily   = DEFAULT_CPU_FAMILY;
229    g_cpuFeatures = 0;
230    g_cpuCount    = 1;
231
232    cpuinfo_len = read_file("/proc/cpuinfo", cpuinfo, sizeof cpuinfo);
233    D("cpuinfo_len is (%d):\n%.*s\n", cpuinfo_len,
234      cpuinfo_len >= 0 ? cpuinfo_len : 0, cpuinfo);
235
236    if (cpuinfo_len < 0)  /* should not happen */ {
237        return;
238    }
239
240    /* Count the CPU cores, the value may be 0 for single-core CPUs */
241    g_cpuCount = count_cpuinfo_field(cpuinfo, cpuinfo_len, "processor");
242    if (g_cpuCount == 0) {
243        g_cpuCount = count_cpuinfo_field(cpuinfo, cpuinfo_len, "Processor");
244        if (g_cpuCount == 0) {
245            g_cpuCount = 1;
246        }
247    }
248
249    D("found cpuCount = %d\n", g_cpuCount);
250
251#ifdef __ARM_ARCH__
252    {
253        char*  features = NULL;
254        char*  architecture = NULL;
255
256        /* Extract architecture from the "CPU Architecture" field.
257         * The list is well-known, unlike the the output of
258         * the 'Processor' field which can vary greatly.
259         *
260         * See the definition of the 'proc_arch' array in
261         * $KERNEL/arch/arm/kernel/setup.c and the 'c_show' function in
262         * same file.
263         */
264        char* cpuArch = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "CPU architecture");
265
266        if (cpuArch != NULL) {
267            char*  end;
268            long   archNumber;
269            int    hasARMv7 = 0;
270
271            D("found cpuArch = '%s'\n", cpuArch);
272
273            /* read the initial decimal number, ignore the rest */
274            archNumber = strtol(cpuArch, &end, 10);
275
276            /* Here we assume that ARMv8 will be upwards compatible with v7
277                * in the future. Unfortunately, there is no 'Features' field to
278                * indicate that Thumb-2 is supported.
279                */
280            if (end > cpuArch && archNumber >= 7) {
281                hasARMv7 = 1;
282            }
283
284            /* Unfortunately, it seems that certain ARMv6-based CPUs
285             * report an incorrect architecture number of 7!
286             *
287             * See http://code.google.com/p/android/issues/detail?id=10812
288             *
289             * We try to correct this by looking at the 'elf_format'
290             * field reported by the 'Processor' field, which is of the
291             * form of "(v7l)" for an ARMv7-based CPU, and "(v6l)" for
292             * an ARMv6-one.
293             */
294            if (hasARMv7) {
295                char* cpuProc = extract_cpuinfo_field(cpuinfo, cpuinfo_len,
296                                                      "Processor");
297                if (cpuProc != NULL) {
298                    D("found cpuProc = '%s'\n", cpuProc);
299                    if (has_list_item(cpuProc, "(v6l)")) {
300                        D("CPU processor and architecture mismatch!!\n");
301                        hasARMv7 = 0;
302                    }
303                    free(cpuProc);
304                }
305            }
306
307            if (hasARMv7) {
308                g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_ARMv7;
309            }
310
311            /* The LDREX / STREX instructions are available from ARMv6 */
312            if (archNumber >= 6) {
313                g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_LDREX_STREX;
314            }
315
316            free(cpuArch);
317        }
318
319        /* Extract the list of CPU features from 'Features' field */
320        char* cpuFeatures = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "Features");
321
322        if (cpuFeatures != NULL) {
323
324            D("found cpuFeatures = '%s'\n", cpuFeatures);
325
326            if (has_list_item(cpuFeatures, "vfpv3"))
327                g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3;
328
329            else if (has_list_item(cpuFeatures, "vfpv3d16"))
330                g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_VFPv3;
331
332            if (has_list_item(cpuFeatures, "neon")) {
333                /* Note: Certain kernels only report neon but not vfpv3
334                    *       in their features list. However, ARM mandates
335                    *       that if Neon is implemented, so must be VFPv3
336                    *       so always set the flag.
337                    */
338                g_cpuFeatures |= ANDROID_CPU_ARM_FEATURE_NEON |
339                                 ANDROID_CPU_ARM_FEATURE_VFPv3;
340            }
341            free(cpuFeatures);
342        }
343    }
344#endif /* __ARM_ARCH__ */
345
346#ifdef __i386__
347    g_cpuFamily = ANDROID_CPU_FAMILY_X86;
348
349    int regs[4];
350
351/* According to http://en.wikipedia.org/wiki/CPUID */
352#define VENDOR_INTEL_b  0x756e6547
353#define VENDOR_INTEL_c  0x6c65746e
354#define VENDOR_INTEL_d  0x49656e69
355
356    x86_cpuid(0, regs);
357    int vendorIsIntel = (regs[1] == VENDOR_INTEL_b &&
358                         regs[2] == VENDOR_INTEL_c &&
359                         regs[3] == VENDOR_INTEL_d);
360
361    x86_cpuid(1, regs);
362    if ((regs[2] & (1 << 9)) != 0) {
363        g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_SSSE3;
364    }
365    if ((regs[2] & (1 << 23)) != 0) {
366        g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_POPCNT;
367    }
368    if (vendorIsIntel && (regs[2] & (1 << 22)) != 0) {
369        g_cpuFeatures |= ANDROID_CPU_X86_FEATURE_MOVBE;
370    }
371#endif
372}
373
374
375AndroidCpuFamily
376android_getCpuFamily(void)
377{
378    pthread_once(&g_once, android_cpuInit);
379    return g_cpuFamily;
380}
381
382
383uint64_t
384android_getCpuFeatures(void)
385{
386    pthread_once(&g_once, android_cpuInit);
387    return g_cpuFeatures;
388}
389
390
391int
392android_getCpuCount(void)
393{
394    pthread_once(&g_once, android_cpuInit);
395    return g_cpuCount;
396}
397