__riscv_hwprobe: don't try to set errno.

When used in an ifunc resolver, errno@plt won't be available. This is
the API the rivos folks contributing to glibc are leaning towards, for
the same reason. Hit by the berberis folks because they don't implement
the syscall so they were trying to set errno to ENOSYS.

Tested by looking at the generated assembler, and also disabling the
vdso (since on actual systems, this will go via the vdso).

Test: treehugger
Change-Id: Ie2779110f141f20efe97cb892fbdefd808b5339b
diff --git a/libc/SECCOMP_ALLOWLIST_COMMON.TXT b/libc/SECCOMP_ALLOWLIST_COMMON.TXT
index 67b662e..aba8303 100644
--- a/libc/SECCOMP_ALLOWLIST_COMMON.TXT
+++ b/libc/SECCOMP_ALLOWLIST_COMMON.TXT
@@ -17,6 +17,9 @@
 int	rt_tgsigqueueinfo(pid_t, pid_t, int, siginfo_t*)	all
 int	restart_syscall()	all
 
+# The public API doesn't set errno, so we call this via inline assembler.
+int riscv_hwprobe(riscv_hwprobe*, size_t, size_t, unsigned long*, unsigned) riscv64
+
 # vfork is used by bionic (and java.lang.ProcessBuilder) on some
 # architectures. (The others use clone(2) directly instead.)
 pid_t	vfork()	arm,x86,x86_64
diff --git a/libc/SYSCALLS.TXT b/libc/SYSCALLS.TXT
index f4f9e1b..e8dde7c 100644
--- a/libc/SYSCALLS.TXT
+++ b/libc/SYSCALLS.TXT
@@ -357,7 +357,6 @@
 
 # riscv64-specific
 int __riscv_flush_icache:riscv_flush_icache(void*, void*, unsigned long) riscv64
-int riscv_hwprobe(riscv_hwprobe*, size_t, size_t, unsigned long*, unsigned) riscv64
 
 # x86-specific
 int     __set_thread_area:set_thread_area(void*) x86
diff --git a/libc/bionic/vdso.cpp b/libc/bionic/vdso.cpp
index e834ec7..d0f01d0 100644
--- a/libc/bionic/vdso.cpp
+++ b/libc/bionic/vdso.cpp
@@ -24,13 +24,13 @@
 #include <sys/cdefs.h>
 #include <sys/hwprobe.h>
 #include <sys/time.h>
+#include <syscall.h>
 #include <time.h>
 #include <unistd.h>
 
 extern "C" int __clock_gettime(int, struct timespec*);
 extern "C" int __clock_getres(int, struct timespec*);
 extern "C" int __gettimeofday(struct timeval*, struct timezone*);
-extern "C" int riscv_hwprobe(struct riscv_hwprobe*, size_t, size_t, unsigned long*, unsigned);
 
 static inline int vdso_return(int result) {
   if (__predict_true(result == 0)) return 0;
@@ -88,9 +88,21 @@
   auto vdso_riscv_hwprobe =
       reinterpret_cast<decltype(&__riscv_hwprobe)>(__libc_globals->vdso[VDSO_RISCV_HWPROBE].fn);
   if (__predict_true(vdso_riscv_hwprobe)) {
-    return vdso_return(vdso_riscv_hwprobe(pairs, pair_count, cpu_count, cpus, flags));
+    return -vdso_riscv_hwprobe(pairs, pair_count, cpu_count, cpus, flags);
   }
-  return riscv_hwprobe(pairs, pair_count, cpu_count, cpus, flags);
+  // Inline the syscall directly in case someone's calling it from an
+  // ifunc resolver where we won't be able to set errno on failure.
+  // (Rather than our usual trick of letting the python-generated
+  // wrapper set errno but saving/restoring errno in cases where the API
+  // is to return an error value rather than setting errno.)
+  register long a0 __asm__("a0") = reinterpret_cast<long>(pairs);
+  register long a1 __asm__("a1") = pair_count;
+  register long a2 __asm__("a2") = cpu_count;
+  register long a3 __asm__("a3") = reinterpret_cast<long>(cpus);
+  register long a4 __asm__("a4") = flags;
+  register long a7 __asm__("a7") = __NR_riscv_hwprobe;
+  __asm__ volatile("ecall" : "=r"(a0) : "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a7));
+  return -a0;
 }
 #endif
 
diff --git a/libc/include/sys/hwprobe.h b/libc/include/sys/hwprobe.h
index f2fd98d..b1a8400 100644
--- a/libc/include/sys/hwprobe.h
+++ b/libc/include/sys/hwprobe.h
@@ -49,7 +49,7 @@
  *
  * A `__cpu_count` of 0 and null `__cpus` means "all online cpus".
  *
- * Returns 0 on success and returns -1 and sets `errno` on failure.
+ * Returns 0 on success and returns an error number on failure.
  */
 int __riscv_hwprobe(struct riscv_hwprobe* _Nonnull __pairs, size_t __pair_count, size_t __cpu_count, unsigned long* _Nullable __cpus, unsigned __flags);