diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # CHANGELOG for crypton
 
+## 2.1.10
+
+* build: ask the processor for AES-NI on every x86 system, not four of them
+  [#307](https://github.com/kazu-yamamoto/crypton/pull/307)
+* fix: ask one place which ARMv8 instruction sets the machine has
+  [#307](https://github.com/kazu-yamamoto/crypton/pull/307)
+
 ## 2.1.9
 
 * feat(rsa): PKCS#1 v1.5 operations that take a digest, and ones that take a DigestInfo
diff --git a/cbits/aes/armv8.c b/cbits/aes/armv8.c
--- a/cbits/aes/armv8.c
+++ b/cbits/aes/armv8.c
@@ -20,12 +20,9 @@
 #include <stdint.h>
 #include <string.h>
 #include <arm_neon.h>
-#if defined(__linux__)
-#include <sys/auxv.h>
-#include <asm/hwcap.h>
-#endif
 #include "crypton_aes.h"
 #include "crypton_bitfn.h"
+#include "crypton_cpu.h"
 
 /*
  * The AES and PMULL instructions are extensions, so a translation unit
@@ -123,21 +120,13 @@
 }
 
 /*
- * Whether the extensions are actually present.
- *
- * They are mandatory on Apple silicon, and on other AArch64 systems the
- * kernel reports them through the auxiliary vector.  A system without them
- * keeps the generic implementation.
+ * Whether the extensions are actually present.  crypton_cpu.c asks the
+ * system once, in the one place that knows how each system answers; a
+ * processor without them keeps the generic implementation.
  */
 int crypton_aes_armv8_available(void)
 {
-#if defined(__APPLE__)
-	return 1;
-#elif defined(__linux__)
-	return (getauxval(AT_HWCAP) & HWCAP_AES) != 0;
-#else
-	return 0;
-#endif
+	return (crypton_arm_features() & CRYPTON_ARM_AES) != 0;
 }
 
 /*
@@ -358,13 +347,7 @@
 
 int crypton_aes_armv8_pmull_available(void)
 {
-#if defined(__APPLE__)
-	return 1;
-#elif defined(__linux__)
-	return (getauxval(AT_HWCAP) & HWCAP_PMULL) != 0;
-#else
-	return 0;
-#endif
+	return (crypton_arm_features() & CRYPTON_ARM_PMULL) != 0;
 }
 
 /*
diff --git a/cbits/crypton_cpu.c b/cbits/crypton_cpu.c
--- a/cbits/crypton_cpu.c
+++ b/cbits/crypton_cpu.c
@@ -60,6 +60,114 @@
     CRYPTON_ARMCAP_NEON;
 #endif
 
+#if defined(__aarch64__)
+#if defined(__APPLE__)
+#include <sys/sysctl.h>
+#elif defined(__linux__)
+#include <sys/auxv.h>
+#include <asm/hwcap.h>
+#elif defined(__FreeBSD__)
+#include <sys/auxv.h>
+#endif
+
+/*
+ * The HWCAP bit positions are the ARM ELF ABI's, so every system that
+ * reports through the auxiliary vector agrees on them.  What differs is
+ * AT_HWCAP itself -- 16 on Linux, 25 on FreeBSD -- and that comes from each
+ * system's own header, which is why the tag is never written out here.
+ * Defined only where the system's headers did not define them, the way
+ * compiler-rt does it, so that a system which reports through the auxiliary
+ * vector without shipping the ARM names still compiles.
+ */
+#ifndef HWCAP_AES
+#define HWCAP_AES    (1 << 3)
+#endif
+#ifndef HWCAP_PMULL
+#define HWCAP_PMULL  (1 << 4)
+#endif
+#ifndef HWCAP_SHA1
+#define HWCAP_SHA1   (1 << 5)
+#endif
+#ifndef HWCAP_SHA2
+#define HWCAP_SHA2   (1 << 6)
+#endif
+#ifndef HWCAP_SHA3
+#define HWCAP_SHA3   (1 << 17)
+#endif
+#ifndef HWCAP_SHA512
+#define HWCAP_SHA512 (1 << 21)
+#endif
+
+#if defined(__APPLE__)
+static int apple_has(const char *name)
+{
+	int v = 0;
+	size_t n = sizeof(v);
+
+	if (sysctlbyname(name, &v, &n, NULL, 0) != 0)
+		return 0;
+	return v != 0;
+}
+#endif
+
+/*
+ * Systems still answering zero, and what each would need:
+ *
+ *   OpenBSD, NetBSD   sysctl on machdep.id_aa64isar0, which hands out the
+ *                     ID_AA64ISAR0_EL1 fields rather than a HWCAP word, so
+ *                     it is a different shape of answer rather than another
+ *                     tag.
+ *   Windows on ARM    IsProcessorFeaturePresent with
+ *                     PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE, which covers
+ *                     AES, PMULL, SHA-1 and SHA-256 as one bit and says
+ *                     nothing about SHA-512 or SHA-3.  An ARM64 Windows
+ *                     guest answers 1 to it, but GHC has no native ARM64
+ *                     Windows target: there it builds x86-64 and the
+ *                     emulator reports AES-NI, so this file is not reached.
+ *
+ * Neither is written here because neither can be built and run to see it
+ * work, and an untested answer about whether a machine has AES is worse
+ * than the honest zero it replaces.
+ */
+unsigned int crypton_arm_features(void)
+{
+	static unsigned int features;
+	static int resolved;
+
+	if (!resolved) {
+		unsigned int f = 0;
+#if defined(__APPLE__)
+		/* AES, PMULL, SHA-1 and SHA-256 are not optional on Apple
+		 * silicon.  The two later ones are. */
+		f = CRYPTON_ARM_AES | CRYPTON_ARM_PMULL
+		  | CRYPTON_ARM_SHA1 | CRYPTON_ARM_SHA2;
+		if (apple_has("hw.optional.arm.FEAT_SHA512"))
+			f |= CRYPTON_ARM_SHA512;
+		if (apple_has("hw.optional.arm.FEAT_SHA3"))
+			f |= CRYPTON_ARM_SHA3;
+#else
+		unsigned long cap = 0;
+
+#if defined(__linux__)
+		cap = getauxval(AT_HWCAP);
+#elif defined(__FreeBSD__)
+		if (elf_aux_info(AT_HWCAP, &cap, sizeof(cap)) != 0)
+			cap = 0;
+#endif
+		if (cap & HWCAP_AES)    f |= CRYPTON_ARM_AES;
+		if (cap & HWCAP_PMULL)  f |= CRYPTON_ARM_PMULL;
+		if (cap & HWCAP_SHA1)   f |= CRYPTON_ARM_SHA1;
+		if (cap & HWCAP_SHA2)   f |= CRYPTON_ARM_SHA2;
+		if (cap & HWCAP_SHA512) f |= CRYPTON_ARM_SHA512;
+		if (cap & HWCAP_SHA3)   f |= CRYPTON_ARM_SHA3;
+#endif
+		features = f;
+		resolved = 1;
+	}
+	return features;
+}
+#endif /* __aarch64__ */
+
 #ifdef ARCH_X86
 static void cpuid(uint32_t info, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx)
 {
diff --git a/cbits/crypton_cpu.h b/cbits/crypton_cpu.h
--- a/cbits/crypton_cpu.h
+++ b/cbits/crypton_cpu.h
@@ -98,6 +98,25 @@
 extern unsigned int crypton_armcap_P;
 #endif
 
+/*
+ * Which of the optional ARMv8 instruction sets this processor has.
+ *
+ * Asked here rather than in each file that wants one.  Five files used to
+ * carry the same three-way conditional -- Apple, Linux, and otherwise zero
+ * -- and the "otherwise" is not a statement about the processor but about
+ * which systems someone had thought of.  It is what left FreeBSD running
+ * the table-driven AES and the C SHA on hardware that has the
+ * instructions.  In one place the next system is added once.
+ */
+#if defined(__aarch64__)
+#define CRYPTON_ARM_AES    (1u << 0)
+#define CRYPTON_ARM_PMULL  (1u << 1)
+#define CRYPTON_ARM_SHA1   (1u << 2)
+#define CRYPTON_ARM_SHA2   (1u << 3)
+#define CRYPTON_ARM_SHA512 (1u << 4)
+#define CRYPTON_ARM_SHA3   (1u << 5)
+unsigned int crypton_arm_features(void);
+#endif
 #ifdef USE_AESNI
 void crypton_aesni_initialize_hw(void (*init_table)(int, int));
 #else
diff --git a/cbits/sha1_armv8.c b/cbits/sha1_armv8.c
--- a/cbits/sha1_armv8.c
+++ b/cbits/sha1_armv8.c
@@ -13,10 +13,7 @@
 
 #include <stdint.h>
 #include <arm_neon.h>
-#if defined(__linux__)
-#include <sys/auxv.h>
-#include <asm/hwcap.h>
-#endif
+#include "crypton_cpu.h"
 
 /*
  * The instructions are an extension, so a translation unit compiled for
@@ -159,11 +156,5 @@
  */
 int crypton_sha1_armv8_available(void)
 {
-#if defined(__APPLE__)
-	return 1;
-#elif defined(__linux__)
-	return (getauxval(AT_HWCAP) & HWCAP_SHA1) != 0;
-#else
-	return 0;
-#endif
+	return (crypton_arm_features() & CRYPTON_ARM_SHA1) != 0;
 }
diff --git a/cbits/sha256_armv8.c b/cbits/sha256_armv8.c
--- a/cbits/sha256_armv8.c
+++ b/cbits/sha256_armv8.c
@@ -12,10 +12,7 @@
 
 #include <stdint.h>
 #include <arm_neon.h>
-#if defined(__linux__)
-#include <sys/auxv.h>
-#include <asm/hwcap.h>
-#endif
+#include "crypton_cpu.h"
 
 /*
  * The SHA-2 instructions are an extension, so a translation unit compiled for
@@ -131,11 +128,5 @@
  */
 int crypton_sha256_armv8_available(void)
 {
-#if defined(__APPLE__)
-	return 1;
-#elif defined(__linux__)
-	return (getauxval(AT_HWCAP) & HWCAP_SHA2) != 0;
-#else
-	return 0;
-#endif
+	return (crypton_arm_features() & CRYPTON_ARM_SHA2) != 0;
 }
diff --git a/cbits/sha3_armv8.c b/cbits/sha3_armv8.c
--- a/cbits/sha3_armv8.c
+++ b/cbits/sha3_armv8.c
@@ -25,12 +25,7 @@
 
 #include <stdint.h>
 #include <arm_neon.h>
-#if defined(__APPLE__)
-#include <sys/sysctl.h>
-#elif defined(__linux__)
-#include <sys/auxv.h>
-#include <asm/hwcap.h>
-#endif
+#include "crypton_cpu.h"
 
 /*
  * The SHA-3 instructions are an ARMv8.2 extension, so a translation unit
@@ -157,16 +152,5 @@
  */
 int crypton_sha3_armv8_available(void)
 {
-#if defined(__APPLE__)
-	int v = 0;
-	size_t n = sizeof(v);
-
-	if (sysctlbyname("hw.optional.arm.FEAT_SHA3", &v, &n, NULL, 0) != 0)
-		return 0;
-	return v != 0;
-#elif defined(__linux__)
-	return (getauxval(AT_HWCAP) & HWCAP_SHA3) != 0;
-#else
-	return 0;
-#endif
+	return (crypton_arm_features() & CRYPTON_ARM_SHA3) != 0;
 }
diff --git a/cbits/sha512_armv8.c b/cbits/sha512_armv8.c
--- a/cbits/sha512_armv8.c
+++ b/cbits/sha512_armv8.c
@@ -14,14 +14,7 @@
 
 #include <stdint.h>
 #include <arm_neon.h>
-#if defined(__linux__)
-#include <sys/auxv.h>
-#include <asm/hwcap.h>
-#endif
-#if defined(__APPLE__)
-#include <sys/sysctl.h>
-#include <string.h>
-#endif
+#include "crypton_cpu.h"
 
 /*
  * The instructions are an extension, so a translation unit compiled for
@@ -142,16 +135,5 @@
  */
 int crypton_sha512_armv8_available(void)
 {
-#if defined(__APPLE__)
-	int v = 0;
-	size_t n = sizeof(v);
-
-	if (sysctlbyname("hw.optional.arm.FEAT_SHA512", &v, &n, NULL, 0) != 0)
-		return 0;
-	return v != 0;
-#elif defined(__linux__)
-	return (getauxval(AT_HWCAP) & HWCAP_SHA512) != 0;
-#else
-	return 0;
-#endif
+	return (crypton_arm_features() & CRYPTON_ARM_SHA512) != 0;
 }
diff --git a/crypton.cabal b/crypton.cabal
--- a/crypton.cabal
+++ b/crypton.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               crypton
-version:            2.1.9
+version:            2.1.10
 -- crypton's own code is BSD-3-Clause.  The parts of
 -- cbits/aes/gcm_fused_x86.c that follow picotls's fusion are MIT, and the
 -- vendored s2n-bignum assembly in cbits/s2n is taken under ISC; each has
@@ -792,7 +792,18 @@
                     cbits/asm/sha1-armv8-linux64.S
                     cbits/asm/sha256-armv8-linux64.S
 
-    if ((flag(support_aesni) && (((os(linux) || os(freebsd)) || os(osx)) || os(windows))) && (arch(i386) || arch(x86_64)))
+    -- No operating system condition.  The processor is asked with cpuid,
+    -- which no kernel is involved in, and crypton_cpu.h turns USE_AESNI on
+    -- from __i386__ or __x86_64__ alone -- so nothing here was ever decided
+    -- by which system it is.  The list that used to stand here named linux,
+    -- freebsd and osx when it arrived from cipher-aes in 2014 and gained
+    -- windows later, each time because someone wanted that one system; it
+    -- was never a statement that the rest could not do this.  What it did
+    -- instead was send OpenBSD, NetBSD, DragonFly, Solaris and illumos down
+    -- the table-driven fallback on processors that have had AES-NI since
+    -- 2010.  The object format still has a say further down, where the GCM
+    -- assembly is chosen, and there ELF is the default.
+    if (flag(support_aesni) && (arch(i386) || arch(x86_64)))
         cc-options: -DWITH_AESNI
         c-sources:
             cbits/aes/generic.c
@@ -853,7 +864,7 @@
     -- branch had already named every file it names.  Cabal drops the repeats,
     -- so nothing was built twice, but the line read as the fallback for a
     -- platform with no AES instructions and was not one.
-    if !((flag(support_aesni) && arch(aarch64)) || ((flag(support_aesni) && (((os(linux) || os(freebsd)) || os(osx)) || os(windows))) && (arch(i386) || arch(x86_64))))
+    if !((flag(support_aesni) && arch(aarch64)) || (flag(support_aesni) && (arch(i386) || arch(x86_64))))
         c-sources:
             cbits/aes/generic.c
             cbits/aes/gf.c
