chiark / gitweb /
Merge branch '2.5.x'
[catacomb] / base / dispatch.c
1 /* -*-c-*-
2  *
3  * CPU-specific dispatch
4  *
5  * (c) 2015 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Catacomb.
11  *
12  * Catacomb is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU Library General Public License as
14  * published by the Free Software Foundation; either version 2 of the
15  * License, or (at your option) any later version.
16  *
17  * Catacomb is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with Catacomb; if not, write to the Free
24  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25  * MA 02111-1307, USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include "config.h"
31
32 #include <ctype.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 #include <mLib/macros.h>
39
40 #include "dispatch.h"
41
42 /*----- Intel x86/AMD64 feature probing -----------------------------------*/
43
44 #if CPUFAM_X86 || CPUFAM_AMD64
45
46 #  define CPUID1D_SSE2 (1u << 26)
47 #  define CPUID1D_FXSR (1u << 24)
48 #  define CPUID1C_PCLMUL (1u << 1)
49 #  define CPUID1C_SSSE3 (1u << 9)
50 #  define CPUID1C_AESNI (1u << 25)
51 #  define CPUID1C_AVX (1u << 28)
52 #  define CPUID1C_RDRAND (1u << 30)
53
54 struct cpuid { unsigned a, b, c, d; };
55 extern int dispatch_x86ish_cpuid(struct cpuid *, unsigned a, unsigned c);
56 extern int dispatch_x86ish_xmmregisters_p(void);
57
58 static void cpuid(struct cpuid *cc, unsigned a, unsigned c)
59 {
60   int rc = dispatch_x86ish_cpuid(cc, a, c);
61   if (rc)
62     dispatch_debug("CPUID instruction not available");
63   else
64     dispatch_debug("CPUID(%08x, %08x) -> %08x, %08x, %08x, %08x",
65                    a, c, cc->a, cc->b, cc->c, cc->d);
66 }
67
68 static unsigned cpuid_maxleaf(void)
69   { struct cpuid c; cpuid(&c, 0, 0); return (c.a); }
70
71 /* --- @cpuid_features_p@ --- *
72  *
73  * Arguments:   @unsigned dbits@ = bits to check in EDX
74  *              @unsigned cbits@ = bits to check in ECX
75  *
76  * Returns:     Nonzero if all the requested bits are set in the CPUID result
77  *              on leaf 1.
78  */
79
80 static int cpuid_features_p(unsigned dbits, unsigned cbits)
81 {
82   struct cpuid c;
83   if (cpuid_maxleaf() < 1) return (0);
84   cpuid(&c, 1, 0);
85   return ((c.d & dbits) == dbits && (c.c & cbits) == cbits);
86 }
87
88 /* --- @xmm_registers_available_p@ --- *
89  *
90  * Arguments:   ---
91  *
92  * Returns:     Nonzero if the operating system has made the XMM registers
93  *              available for use.
94  */
95
96 static int xmm_registers_available_p(void)
97 {
98   int f = dispatch_x86ish_xmmregisters_p();
99
100   dispatch_debug("XMM registers %savailable", f ? "" : "not ");
101   return (f);
102 }
103
104 #endif
105
106 /*----- General feature probing using auxiliary vectors -------------------*/
107
108 /* Try to find the system's definitions for auxiliary vector entries. */
109 #ifdef HAVE_SYS_AUXV_H
110 #  include <sys/auxv.h>
111 #endif
112 #ifdef HAVE_LINUX_AUXVEC_H
113 #  include <linux/auxvec.h>
114 #endif
115 #ifdef HAVE_ASM_HWCAP_H
116 #  include <asm/hwcap.h>
117 #endif
118
119 /* The type of entries in the auxiliary vector.  I'm assuming that `unsigned
120  * long' matches each platform's word length; if this is false then we'll
121  * need some host-specific tweaking here.
122  */
123 union auxval { long i; unsigned long u; const void *p; };
124 struct auxentry { unsigned long type; union auxval value; };
125
126 /* Register each CPU family's interest in the auxiliary vector.  Make sure
127  * that the necessary entry types are defined.  This is primarily ordered by
128  * entry type to minimize duplication.
129  */
130 #if defined(AT_HWCAP) && CPUFAM_ARMEL
131 #  define WANT_ANY 1
132 #  define WANT_AT_HWCAP(_) _(AT_HWCAP, u, hwcap)
133 #endif
134
135 #if defined(AT_HWCAP) && CPUFAM_ARM64
136 #  define WANT_ANY 1
137 #  define WANT_AT_HWCAP(_) _(AT_HWCAP, u, hwcap)
138 #endif
139
140 #if defined(AT_HWCAP2) && CPUFAM_ARMEL
141 #  define WANT_ANY 1
142 #  define WANT_AT_HWCAP2(_) _(AT_HWCAP2, u, hwcap2)
143 #endif
144
145 /* If we couldn't find any interesting entries then we can switch all of this
146  * machinery off.  Also do that if we have no means for atomic updates.
147  */
148 #if WANT_ANY && CPU_DISPATCH_P
149
150 /* The main output of this section is a bitmask of detected features.  The
151  * least significant bit will be set if we've tried to probe.  Always access
152  * this using `DISPATCH_LOAD' and `DISPATCH_STORE'.
153  */
154 static unsigned hwcaps = 0;
155
156 /* For each potentially interesting type which turned out not to exist or be
157  * wanted, define a dummy macro for the sake of the next step.
158  */
159 #ifndef WANT_AT_HWCAP
160 #  define WANT_AT_HWCAP(_)
161 #endif
162 #ifndef WANT_AT_HWCAP2
163 #  define WANT_AT_HWCAP2(_)
164 #endif
165
166 /* For each CPU family, define two lists.
167  *
168  *   * `WANTAUX' is a list of the `WANT_AT_MUMBLE' macros which the CPU
169  *     family tried to register interest in above.  Each entry contains the
170  *     interesting auxiliary vector entry type, the name of the union branch
171  *     for its value, and the name of the slot in `struct auxprobe' in which
172  *     to store the value.
173  *
174  *   * `CAPMAP' is a list describing the output features which the CPU family
175  *     intends to satisfy from the auxiliary vector.  Each entry contains a
176  *     feature name suffix, and the token name (for `check_env').
177  */
178 #if CPUFAM_ARMEL
179 #  define WANTAUX(_)                                                    \
180         WANT_AT_HWCAP(_)                                                \
181         WANT_AT_HWCAP2(_)
182 #  define CAPMAP(_)                                                     \
183         _(ARM_VFP, "arm:vfp")                                           \
184         _(ARM_NEON, "arm:neon")                                         \
185         _(ARM_V4, "arm:v4")                                             \
186         _(ARM_D32, "arm:d32")                                           \
187         _(ARM_AES, "arm:aes")                                           \
188         _(ARM_PMULL, "arm:pmull")
189 #endif
190 #if CPUFAM_ARM64
191 #  define WANTAUX(_)                                                    \
192         WANT_AT_HWCAP(_)
193 #  define CAPMAP(_)                                                     \
194         _(ARM_AES, "arm:aes")                                           \
195         _(ARM_PMULL, "arm:pmull")
196 #endif
197
198 /* Build the bitmask for `hwcaps' from the `CAPMAP' list. */
199 enum {
200   HFI_PROBED = 0,
201 #define HFI__ENUM(feat, tok) HFI_##feat,
202   CAPMAP(HFI__ENUM)
203 #undef HFI__ENUM
204   HFI__END
205 };
206 enum {
207   HF_PROBED = 1,
208 #define HF__FLAG(feat, tok) HF_##feat = 1 << HFI_##feat,
209   CAPMAP(HF__FLAG)
210 #undef HF__FLAG
211   HF__END
212 };
213
214 /* Build a structure in which we can capture the interesting data from the
215  * auxiliary vector.
216  */
217 #define AUXUTYPE_i long
218 #define AUXUTYPE_u unsigned long
219 #define AUXUTYPE_p const void *
220 struct auxprobe {
221 #define AUXPROBE__SLOT(type, ubranch, slot) AUXUTYPE_##ubranch slot;
222   WANTAUX(AUXPROBE__SLOT)
223 #undef AUXPROBE_SLOT
224 };
225
226 /* --- @probe_hwcaps@ --- *
227  *
228  * Arguments:   ---
229  *
230  * Returns:     ---
231  *
232  * Use:         Attempt to find the auxiliary vector (which is well hidden)
233  *              and discover interesting features from it.
234  */
235
236 static void probe_hwcaps(void)
237 {
238   unsigned hw = HF_PROBED;
239   struct auxprobe probed = { 0 };
240
241   /* Populate `probed' with the information we manage to retrieve from the
242    * auxiliary vector.  Slots we couldn't find are left zero-valued.
243    */
244 #if defined(HAVE_GETAUXVAL)
245   /* Shiny new libc lets us request individual entry types.  This is almost
246    * too easy.
247    */
248 #  define CAP__GET(type, ubranch, slot)                                 \
249         probed.slot = (AUXUTYPE_##ubranch)getauxval(type);
250   WANTAUX(CAP__GET)
251 #else
252   /* Otherwise we're a bit stuck, really.  Modern Linux kernels make a copy
253    * of the vector available in `/procc' so we could try that.
254    *
255    * The usual place is stuck on the end of the environment vector, but that
256    * may well have moved, and we have no way of telling whether it has or
257    * whether there was ever an auxiliary vector there at all; so don't do
258    * that.
259    */
260   {
261     FILE *fp = 0;
262     unsigned char *p = 0, *q = 0;
263     const struct auxentry *a;
264     size_t sz, off, n;
265
266     /* Open the file and read it into a memory chunk. */
267     if ((fp = fopen("/proc/self/auxv", "rb")) == 0) goto clean;
268     sz = 4096; off = 0;
269     if ((p = malloc(sz)) == 0) goto clean;
270     for (;;) {
271       n = fread(p + off, 1, sz - off, fp);
272       off += n;
273       if (off < sz) break;
274       sz *= 2; if ((q = realloc(p, sz)) == 0) break;
275       p = q;
276     }
277
278     /* Work through the vector (or as much of it as we found) and extract the
279      * types we're interested in.
280      */
281     for (a = (const struct auxentry *)p,
282            n = sz/sizeof(struct auxentry);
283          n--; a++) {
284       switch (a->type) {
285 #define CAP__SWITCH(type, ubranch, slot)                                \
286         case type: probed.slot = a->value.ubranch; break;
287         WANTAUX(CAP__SWITCH)
288         case AT_NULL: goto clean;
289       }
290     }
291
292   clean:
293     if (p) free(p);
294     if (fp) fclose(fp);
295   }
296 #endif
297
298   /* Each CPU family now has to pick through what was found and stashed in
299    * `probed', and set the appropriate flag bits in `hw'.
300    */
301 #if CPUFAM_ARMEL
302   if (probed.hwcap & HWCAP_VFPv3) hw |= HF_ARM_VFP;
303   if (probed.hwcap & HWCAP_NEON) hw |= HF_ARM_NEON;
304   if (probed.hwcap & HWCAP_VFPD32) hw |= HF_ARM_D32;
305   if (probed.hwcap & HWCAP_VFPv4) hw |= HF_ARM_V4;
306 #  ifdef HWCAP2_AES
307   if (probed.hwcap2 & HWCAP2_AES) hw |= HF_ARM_AES;
308 #  endif
309 #  ifdef HWCAP2_PMULL
310   if (probed.hwcap2 & HWCAP2_PMULL) hw |= HF_ARM_PMULL;
311 #  endif
312 #endif
313 #if CPUFAM_ARM64
314   if (probed.hwcap & HWCAP_AES) hw |= HF_ARM_AES;
315   if (probed.hwcap & HWCAP_PMULL) hw |= HF_ARM_PMULL;
316 #endif
317
318   /* Store the bitmask of features we probed for everyone to see. */
319   DISPATCH_STORE(hwcaps, hw);
320
321   /* Finally, make a report about the things we found.  (Doing this earlier
322    * will pointlessly widen the window in which multiple threads will do the
323    * above auxiliary-vector probing.)
324    */
325 #define CAP__DEBUG(feat, tok)                                           \
326   dispatch_debug("check auxv for feature `%s': %s", tok,                \
327                  hw & HF_##feat ? "available" : "absent");
328   CAPMAP(CAP__DEBUG)
329 #undef CAP__DEBUG
330 }
331
332 /* --- @get_hwcaps@ --- *
333  *
334  * Arguments:   ---
335  *
336  * Returns:     A mask of hardware capabilities and other features, as probed
337  *              from the auxiliary vector.
338  */
339
340 static unsigned get_hwcaps(void)
341 {
342   unsigned hw;
343
344   DISPATCH_LOAD(hwcaps, hw);
345   if (!(hwcaps & HF_PROBED)) { probe_hwcaps(); DISPATCH_LOAD(hwcaps, hw); }
346   return (hw);
347 }
348
349 #endif
350
351 /*----- External interface ------------------------------------------------*/
352
353 /* --- @dispatch_debug@ --- *
354  *
355  * Arguments:   @const char *fmt@ = a format string
356  *              @...@ = additional arguments
357  *
358  * Returns:     ---
359  *
360  * Use:         Writes a formatted message to standard output if dispatch
361  *              debugging is enabled.
362  */
363
364 void dispatch_debug(const char *fmt, ...)
365 {
366   va_list ap;
367   const char *e = getenv("CATACOMB_CPUDISPATCH_DEBUG");
368
369   if (e && *e != 'n' && *e != '0') {
370     va_start(ap, fmt);
371     fputs("Catacomb CPUDISPATCH: ", stderr);
372     vfprintf(stderr, fmt, ap);
373     fputc('\n', stderr);
374     va_end(ap);
375   }
376 }
377
378 /* --- @check_env@ --- *
379  *
380  * Arguments:   @const char *ftok@ = feature token
381  *
382  * Returns:     Zero if the feature is forced off; positive if it's forced
383  *              on; negative if the user hasn't decided.
384  *
385  * Use:         Checks the environment variable `CATACOMB_CPUFEAT' for the
386  *              feature token @ftok@.  The variable, if it exists, should be
387  *              a space-separated sequence of `+tok' and `-tok' items.  These
388  *              tokens may end in `*', which matches any suffix.
389  */
390
391 static int IGNORABLE check_env(const char *ftok)
392 {
393   const char *p, *q, *pp;
394   int d;
395
396   p = getenv("CATACOMB_CPUFEAT");
397   if (!p) return (-1);
398
399   for (;;) {
400     while (isspace((unsigned char)*p)) p++;
401     if (!*p) return (-1);
402     switch (*p) {
403       case '+': d = +1; p++; break;
404       case '-': d =  0; p++; break;
405       default:  d = -1;      break;
406     }
407     for (q = p; *q && !isspace((unsigned char)*q); q++);
408     if (d >= 0) {
409       for (pp = ftok; p < q && *pp && *p == *pp; p++, pp++);
410       if ((p == q && !*pp) || (*p == '*' && p + 1 == q)) return (d);
411     }
412     p = q;
413   }
414   return (-1);
415 }
416
417 /* --- @cpu_feature_p@ --- *
418  *
419  * Arguments:   @unsigned feat@ = a @CPUFEAT_...@ code
420  *
421  * Returns:     Nonzero if the feature is available.
422  */
423
424 #include <stdio.h>
425
426 static int IGNORABLE
427   feat_debug(const char *ftok, const char *check, int verdict)
428 {
429   if (verdict >= 0) {
430     dispatch_debug("feature `%s': %s -> %s", ftok, check,
431                    verdict ? "available" : "absent");
432   }
433   return (verdict);
434 }
435
436 int cpu_feature_p(int feat)
437 {
438   int IGNORABLE f;
439   IGNORE(f);
440 #define CASE_CPUFEAT(feat, ftok, cond) case CPUFEAT_##feat:             \
441   if ((f = feat_debug(ftok, "environment override",                     \
442                       check_env(ftok))) >= 0)                           \
443     return (f);                                                         \
444   else                                                                  \
445     return (feat_debug(ftok, "runtime probe", cond));
446
447   switch (feat) {
448 #if CPUFAM_X86 || CPUFAM_AMD64
449     CASE_CPUFEAT(X86_SSE2, "x86:sse2",
450                  cpuid_features_p(CPUID1D_SSE2, 0) &&
451                  xmm_registers_available_p());
452     CASE_CPUFEAT(X86_AESNI, "x86:aesni",
453                  cpuid_features_p(CPUID1D_SSE2, CPUID1C_AESNI) &&
454                  xmm_registers_available_p());
455     CASE_CPUFEAT(X86_RDRAND, "x86:rdrand",
456                  cpuid_features_p(0, CPUID1C_RDRAND));
457     CASE_CPUFEAT(X86_AVX, "x86:avx",
458                  cpuid_features_p(0, CPUID1C_AVX) &&
459                  xmm_registers_available_p());
460     CASE_CPUFEAT(X86_SSSE3, "x86:ssse3",
461                  cpuid_features_p(0, CPUID1C_SSSE3) &&
462                  xmm_registers_available_p());
463     CASE_CPUFEAT(X86_PCLMUL, "x86:pclmul",
464                  cpuid_features_p(0, CPUID1C_PCLMUL) &&
465                  xmm_registers_available_p());
466 #endif
467 #ifdef CAPMAP
468 #  define FEATP__CASE(feat, tok)                                        \
469         CASE_CPUFEAT(feat, tok, get_hwcaps() & HF_##feat)
470     CAPMAP(FEATP__CASE)
471 #undef FEATP__CASE
472 #endif
473     default:
474       dispatch_debug("denying unknown feature %d", feat);
475       return (0);
476   }
477 #undef CASE_CPUFEAT
478 }
479
480 /*----- That's all, folks -------------------------------------------------*/