sysconf(): implement cache queries.

This is a bit disappointing. I'd not implemented this in the past
because it wasn't available on all platforms, and -- although the
riscv64 implementation was just a cool optimization -- I thought that
the /sys stuff was actually portable, until I ran it on arm64 hardware.
So here we have getauxval() for riscv64, /sys for x86-64, and our best
guess based on ctr_el0 for arm64.

Bug: http://b/294034962
Test: ran tests on the host, an arm64 device, and riscv64 host and qemu
Change-Id: I420b69b976d30668d4d2ac548c4229e2a4eafb20
diff --git a/libc/bionic/sysconf.cpp b/libc/bionic/sysconf.cpp
index 3906e2e..edbdef1 100644
--- a/libc/bionic/sysconf.cpp
+++ b/libc/bionic/sysconf.cpp
@@ -41,6 +41,107 @@
 #include "platform/bionic/page.h"
 #include "private/bionic_tls.h"
 
+struct sysconf_cache {
+  long size, assoc, linesize;
+
+  static sysconf_cache from_size_and_geometry(int size_id, int geometry_id) {
+    sysconf_cache result;
+    result.size = getauxval(size_id);
+    unsigned long geometry = getauxval(geometry_id);
+    result.assoc = geometry >> 16;
+    result.linesize = geometry & 0xffff;
+    return result;
+  }
+};
+
+struct sysconf_caches {
+  sysconf_cache l1_i, l1_d, l2, l3, l4;
+};
+
+#if defined(__riscv)
+
+static sysconf_caches* __sysconf_caches() {
+  static sysconf_caches cached = []{
+    sysconf_caches info = {};
+    // riscv64 kernels conveniently hand us all this information.
+    info.l1_i = sysconf_cache::from_size_and_geometry(AT_L1I_CACHESIZE, AT_L1I_CACHEGEOMETRY);
+    info.l1_d = sysconf_cache::from_size_and_geometry(AT_L1D_CACHESIZE, AT_L1D_CACHEGEOMETRY);
+    info.l2 = sysconf_cache::from_size_and_geometry(AT_L2_CACHESIZE, AT_L2_CACHEGEOMETRY);
+    info.l3 = sysconf_cache::from_size_and_geometry(AT_L3_CACHESIZE, AT_L3_CACHEGEOMETRY);
+    return info;
+  }();
+  return &cached;
+}
+
+#elif defined(__aarch64__)
+
+static sysconf_caches* __sysconf_caches() {
+  static sysconf_caches cached = []{
+    sysconf_caches info = {};
+    // arm64 is especially limited. We can infer the L1 line sizes, but that's it.
+    uint64_t ctr_el0;
+    __asm__ __volatile__("mrs %0, ctr_el0" : "=r"(ctr_el0));
+    info.l1_i.linesize = 4 << (ctr_el0 & 0xf);
+    info.l1_d.linesize = 4 << ((ctr_el0 >> 16) & 0xf);
+    return info;
+  }();
+  return &cached;
+}
+
+#else
+
+long __sysconf_fread_long(const char* path) {
+  long result = 0;
+  FILE* fp = fopen(path, "re");
+  if (fp != nullptr) {
+    fscanf(fp, "%ld", &result);
+    fclose(fp);
+  }
+  return result;
+}
+
+static sysconf_caches* __sysconf_caches() {
+  static sysconf_caches cached = []{
+    sysconf_caches info = {};
+    char path[64];
+    for (int i = 0; i < 4; i++) {
+      sysconf_cache c;
+
+      snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/size", i);
+      c.size = __sysconf_fread_long(path) * 1024;
+      if (c.size == 0) break;
+
+      snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/ways_of_associativity", i);
+      c.assoc = __sysconf_fread_long(path);
+
+      snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/coherency_line_size", i);
+      c.linesize = __sysconf_fread_long(path);
+
+      snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/level", i);
+      int level = __sysconf_fread_long(path);
+      if (level == 1) {
+        snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu0/cache/index%d/type", i);
+        FILE* fp = fopen(path, "re");
+        char type = fgetc(fp);
+        fclose(fp);
+        if (type == 'D') {
+          info.l1_d = c;
+        } else if (type == 'I') {
+          info.l1_i = c;
+        }
+      } else if (level == 2) {
+        info.l2 = c;
+      } else if (level == 3) {
+        info.l3 = c;
+      }
+    }
+    return info;
+  }();
+  return &cached;
+}
+
+#endif
+
 static long __sysconf_rlimit(int resource) {
   rlimit rl;
   getrlimit(resource, &rl);
@@ -218,23 +319,21 @@
     case _SC_XOPEN_STREAMS:     return -1;            // Obsolescent in POSIX.1-2008.
     case _SC_XOPEN_UUCP:        return -1;
 
-    // We do not have actual implementations for cache queries.
-    // It's valid to return 0 as the result is unknown.
-    case _SC_LEVEL1_ICACHE_SIZE:      return 0;
-    case _SC_LEVEL1_ICACHE_ASSOC:     return 0;
-    case _SC_LEVEL1_ICACHE_LINESIZE:  return 0;
-    case _SC_LEVEL1_DCACHE_SIZE:      return 0;
-    case _SC_LEVEL1_DCACHE_ASSOC:     return 0;
-    case _SC_LEVEL1_DCACHE_LINESIZE:  return 0;
-    case _SC_LEVEL2_CACHE_SIZE:       return 0;
-    case _SC_LEVEL2_CACHE_ASSOC:      return 0;
-    case _SC_LEVEL2_CACHE_LINESIZE:   return 0;
-    case _SC_LEVEL3_CACHE_SIZE:       return 0;
-    case _SC_LEVEL3_CACHE_ASSOC:      return 0;
-    case _SC_LEVEL3_CACHE_LINESIZE:   return 0;
-    case _SC_LEVEL4_CACHE_SIZE:       return 0;
-    case _SC_LEVEL4_CACHE_ASSOC:      return 0;
-    case _SC_LEVEL4_CACHE_LINESIZE:   return 0;
+    case _SC_LEVEL1_ICACHE_SIZE:      return __sysconf_caches()->l1_i.size;
+    case _SC_LEVEL1_ICACHE_ASSOC:     return __sysconf_caches()->l1_i.assoc;
+    case _SC_LEVEL1_ICACHE_LINESIZE:  return __sysconf_caches()->l1_i.linesize;
+    case _SC_LEVEL1_DCACHE_SIZE:      return __sysconf_caches()->l1_d.size;
+    case _SC_LEVEL1_DCACHE_ASSOC:     return __sysconf_caches()->l1_d.assoc;
+    case _SC_LEVEL1_DCACHE_LINESIZE:  return __sysconf_caches()->l1_d.linesize;
+    case _SC_LEVEL2_CACHE_SIZE:       return __sysconf_caches()->l2.size;
+    case _SC_LEVEL2_CACHE_ASSOC:      return __sysconf_caches()->l2.assoc;
+    case _SC_LEVEL2_CACHE_LINESIZE:   return __sysconf_caches()->l2.linesize;
+    case _SC_LEVEL3_CACHE_SIZE:       return __sysconf_caches()->l3.size;
+    case _SC_LEVEL3_CACHE_ASSOC:      return __sysconf_caches()->l3.assoc;
+    case _SC_LEVEL3_CACHE_LINESIZE:   return __sysconf_caches()->l3.linesize;
+    case _SC_LEVEL4_CACHE_SIZE:       return __sysconf_caches()->l4.size;
+    case _SC_LEVEL4_CACHE_ASSOC:      return __sysconf_caches()->l4.assoc;
+    case _SC_LEVEL4_CACHE_LINESIZE:   return __sysconf_caches()->l4.linesize;
 
     default:
       errno = EINVAL;