base/dispatch.c: Add (unused) machinery for probing ELF auxilary vector.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 18 May 2016 09:29:03 +0000 (10:29 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 21 May 2016 16:17:27 +0000 (17:17 +0100)
Some platforms don't allow CPU feature-probing from userland, and rely
on the kernel informing processes in some other way.  ELF-ish systems
hide this information in the auxiliary vector, which is very hard to
find.  Add some machinery for doing this anyway.

base/dispatch.c
configure.ac

index 8936ea4..10ae404 100644 (file)
@@ -199,6 +199,199 @@ static int xmm_registers_available_p(void)
 
 #endif
 
+/*----- General feature probing using auxiliary vectors -------------------*/
+
+/* Try to find the system's definitions for auxiliary vector entries. */
+#ifdef HAVE_SYS_AUXV_H
+#  include <sys/auxv.h>
+#else
+#  ifdef HAVE_LINUX_AUXVEC_H
+#    include <linux/auxvec.h>
+#  endif
+#  ifdef HAVE_ASM_HWCAP_H
+#    include <asm/hwcap.h>
+#  endif
+#endif
+
+/* The type of entries in the auxiliary vector.  I'm assuming that `unsigned
+ * long' matches each platform's word length; if this is false then we'll
+ * need some host-specific tweaking here.
+ */
+union auxval { long i; unsigned long u; const void *p; };
+struct auxentry { unsigned long type; union auxval value; };
+
+/* Register each CPU family's interest in the auxiliary vector.  Make sure
+ * that the necessary entry types are defined.  This is primarily ordered by
+ * entry type to minimize duplication.
+ */
+
+/* If we couldn't find any interesting entries then we can switch all of this
+ * machinery off.  Also do that if we have no means for atomic updates.
+ */
+#if WANT_ANY && CPU_DISPATCH_P
+
+/* The main output of this section is a bitmask of detected features.  The
+ * least significant bit will be set if we've tried to probe.  Always access
+ * this using `DISPATCH_LOAD' and `DISPATCH_STORE'.
+ */
+static unsigned hwcaps = 0;
+
+/* For each potentially interesting type which turned out not to exist or be
+ * wanted, define a dummy macro for the sake of the next step.
+ */
+#ifndef WANT_AT_HWCAP
+#  define WANT_AT_HWCAP(_)
+#endif
+
+/* For each CPU family, define two lists.
+ *
+ *   * `WANTAUX' is a list of the `WANT_AT_MUMBLE' macros which the CPU
+ *     family tried to register interest in above.  Each entry contains the
+ *     interesting auxiliary vector entry type, the name of the union branch
+ *     for its value, and the name of the slot in `struct auxprobe' in which
+ *     to store the value.
+ *
+ *   * `CAPMAP' is a list describing the output features which the CPU family
+ *     intends to satisfy from the auxiliary vector.  Each entry contains a
+ *     feature name suffix, and the token name (for `check_env').
+ */
+
+/* Build the bitmask for `hwcaps' from the `CAPMAP' list. */
+enum {
+  HFI_PROBED = 0,
+#define HFI__ENUM(feat, tok) HFI_##feat,
+  CAPMAP(HFI__ENUM)
+#undef HFI__ENUM
+  HFI__END
+};
+enum {
+  HF_PROBED = 1,
+#define HF__FLAG(feat, tok) HF_##feat = 1 << HFI_##feat,
+  CAPMAP(HF__FLAG)
+#undef HF__FLAG
+  HF__END
+};
+
+/* Build a structure in which we can capture the interesting data from the
+ * auxiliary vector.
+ */
+#define AUXUTYPE_i long
+#define AUXUTYPE_u unsigned long
+#define AUXUTYPE_p const void *
+struct auxprobe {
+#define AUXPROBE__SLOT(type, ubranch, slot) AUXUTYPE_##ubranch slot;
+  WANTAUX(AUXPROBE__SLOT)
+#undef AUXPROBE_SLOT
+};
+
+/* --- @probe_hwcaps@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Attempt to find the auxiliary vector (which is well hidden)
+ *             and discover interesting features from it.
+ */
+
+static void probe_hwcaps(void)
+{
+  unsigned hw = HF_PROBED;
+  struct auxprobe probed = { 0 };
+
+  /* Populate `probed' with the information we manage to retrieve from the
+   * auxiliary vector.  Slots we couldn't find are left zero-valued.
+   */
+#if defined(HAVE_GETAUXVAL)
+  /* Shiny new libc lets us request individual entry types.  This is almost
+   * too easy.
+   */
+#  define CAP__GET(type, slot, ubranch)                                        \
+       probed.slot.ubranch = (AUXUTYPE_##ubranch)getauxval(type);
+  WANTAUX(CAP__GET)
+#else
+  /* Otherwise we're a bit stuck, really.  Modern Linux kernels make a copy
+   * of the vector available in `/procc' so we could try that.
+   *
+   * The usual place is stuck on the end of the environment vector, but that
+   * may well have moved, and we have no way of telling whether it has or
+   * whether there was ever an auxiliary vector there at all; so don't do
+   * that.
+   */
+  {
+    FILE *fp = 0;
+    unsigned char *p = 0, *q = 0;
+    const struct auxentry *a;
+    size_t sz, off, n;
+
+    /* Open the file and read it into a memory chunk. */
+    if ((fp = fopen("/proc/self/auxv", "rb")) == 0) goto clean;
+    sz = 4096; off = 0;
+    if ((p = malloc(sz)) == 0) goto clean;
+    for (;;) {
+      n = fread(p + off, 1, sz - off, fp);
+      off += n;
+      if (off < sz) break;
+      sz *= 2; if ((q = realloc(p, sz)) == 0) break;
+      p = q;
+    }
+
+    /* Work through the vector (or as much of it as we found) and extract the
+     * types we're interested in.
+     */
+    for (a = (const struct auxentry *)p,
+          n = sz/sizeof(struct auxentry);
+        n--; a++) {
+      switch (a->type) {
+#define CAP__SWITCH(type, ubranch, slot)                               \
+       case type: probed.slot = a->value.ubranch; break;
+       WANTAUX(CAP__SWITCH)
+      }
+    }
+
+  clean:
+    if (p) free(p);
+    if (fp) fclose(fp);
+  }
+#endif
+
+  /* Each CPU family now has to pick through what was found and stashed in
+   * `probed', and set the appropriate flag bits in `hw'.
+   */
+
+  /* Store the bitmask of features we probed for everyone to see. */
+  DISPATCH_STORE(hwcaps, hw);
+
+  /* Finally, make a report about the things we found.  (Doing this earlier
+   * will pointlessly widen the window in which multiple threads will do the
+   * above auxiliary-vector probing.)
+   */
+#define CAP__DEBUG(feat, tok)                                          \
+  dispatch_debug("check auxv for feature `%s': %s", tok,               \
+                hw & HF_##feat ? "available" : "absent");
+  CAPMAP(CAP__DEBUG)
+#undef CAP__DEBUG
+}
+
+/* --- @get_hwcaps@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    A mask of hardware capabilities and other features, as probed
+ *             from the auxiliary vector.
+ */
+
+static unsigned get_hwcaps(void)
+{
+  unsigned hw;
+
+  DISPATCH_LOAD(hwcaps, hw);
+  if (!(hwcaps & HF_PROBED)) { probe_hwcaps(); DISPATCH_LOAD(hwcaps, hw); }
+  return (hw);
+}
+
+#endif
+
 /*----- External interface ------------------------------------------------*/
 
 /* --- @dispatch_debug@ --- *
@@ -304,6 +497,12 @@ int cpu_feature_p(int feat)
                 xmm_registers_available_p() &&
                 cpuid_features_p(CPUID1D_SSE2, CPUID1C_AESNI));
 #endif
+#ifdef CAPMAP
+#  define FEATP__CASE(feat, tok)                                       \
+       CASE_CPUFEAT(feat, tok, get_hwcaps & HF_##feat)
+    CAPMAP(FEATP__CASE)
+#undef FEATP__CASE
+#endif
     default:
       dispatch_debug("denying unknown feature %d", feat);
       return (0);
index 8a58d78..dcf7028 100644 (file)
@@ -128,6 +128,12 @@ case $CPUFAM in
   *) AC_MSG_RESULT([$CPUFAM/$ABI]) ;;
 esac
 
+dnl Some equipment wanted for checking CPU features at runtime.
+AC_CHECK_HEADERS([asm/hwcap.h])
+AC_CHECK_HEADERS([sys/auxv.h])
+AC_CHECK_HEADERS([linux/auxvec.h])
+AC_CHECK_FUNCS([getauxval])
+
 dnl--------------------------------------------------------------------------
 dnl C programming environment.