| /* |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| * Host specific cpu identification for RISC-V. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "host/cpuinfo.h" |
| |
| #ifdef CONFIG_ASM_HWPROBE_H |
| #include <asm/hwprobe.h> |
| #include <sys/syscall.h> |
| #endif |
| |
| unsigned cpuinfo; |
| static volatile sig_atomic_t got_sigill; |
| |
| static void sigill_handler(int signo, siginfo_t *si, void *data) |
| { |
| /* Skip the faulty instruction */ |
| ucontext_t *uc = (ucontext_t *)data; |
| |
| #ifdef __linux__ |
| uc->uc_mcontext.__gregs[REG_PC] += 4; |
| #elif defined(__OpenBSD__) |
| uc->sc_sepc += 4; |
| #else |
| # error Unsupported OS |
| #endif |
| |
| got_sigill = 1; |
| } |
| |
| /* Called both as constructor and (possibly) via other constructors. */ |
| unsigned __attribute__((constructor)) cpuinfo_init(void) |
| { |
| unsigned left = CPUINFO_ZBA | CPUINFO_ZBB | CPUINFO_ZICOND; |
| unsigned info = cpuinfo; |
| |
| if (info) { |
| return info; |
| } |
| |
| /* Test for compile-time settings. */ |
| #if defined(__riscv_arch_test) && defined(__riscv_zba) |
| info |= CPUINFO_ZBA; |
| #endif |
| #if defined(__riscv_arch_test) && defined(__riscv_zbb) |
| info |= CPUINFO_ZBB; |
| #endif |
| #if defined(__riscv_arch_test) && defined(__riscv_zicond) |
| info |= CPUINFO_ZICOND; |
| #endif |
| left &= ~info; |
| |
| #ifdef CONFIG_ASM_HWPROBE_H |
| if (left) { |
| /* |
| * TODO: glibc 2.40 will introduce <sys/hwprobe.h>, which |
| * provides __riscv_hwprobe and __riscv_hwprobe_one, |
| * which is a slightly cleaner interface. |
| */ |
| struct riscv_hwprobe pair = { .key = RISCV_HWPROBE_KEY_IMA_EXT_0 }; |
| if (syscall(__NR_riscv_hwprobe, &pair, 1, 0, NULL, 0) == 0 |
| && pair.key >= 0) { |
| info |= pair.value & RISCV_HWPROBE_EXT_ZBA ? CPUINFO_ZBA : 0; |
| info |= pair.value & RISCV_HWPROBE_EXT_ZBB ? CPUINFO_ZBB : 0; |
| left &= ~(CPUINFO_ZBA | CPUINFO_ZBB); |
| #ifdef RISCV_HWPROBE_EXT_ZICOND |
| info |= pair.value & RISCV_HWPROBE_EXT_ZICOND ? CPUINFO_ZICOND : 0; |
| left &= ~CPUINFO_ZICOND; |
| #endif |
| } |
| } |
| #endif /* CONFIG_ASM_HWPROBE_H */ |
| |
| if (left) { |
| struct sigaction sa_old, sa_new; |
| |
| memset(&sa_new, 0, sizeof(sa_new)); |
| sa_new.sa_flags = SA_SIGINFO; |
| sa_new.sa_sigaction = sigill_handler; |
| sigaction(SIGILL, &sa_new, &sa_old); |
| |
| if (left & CPUINFO_ZBA) { |
| /* Probe for Zba: add.uw zero,zero,zero. */ |
| got_sigill = 0; |
| asm volatile(".insn r 0x3b, 0, 0x04, zero, zero, zero" |
| : : : "memory"); |
| info |= got_sigill ? 0 : CPUINFO_ZBA; |
| left &= ~CPUINFO_ZBA; |
| } |
| |
| if (left & CPUINFO_ZBB) { |
| /* Probe for Zbb: andn zero,zero,zero. */ |
| got_sigill = 0; |
| asm volatile(".insn r 0x33, 7, 0x20, zero, zero, zero" |
| : : : "memory"); |
| info |= got_sigill ? 0 : CPUINFO_ZBB; |
| left &= ~CPUINFO_ZBB; |
| } |
| |
| if (left & CPUINFO_ZICOND) { |
| /* Probe for Zicond: czero.eqz zero,zero,zero. */ |
| got_sigill = 0; |
| asm volatile(".insn r 0x33, 5, 0x07, zero, zero, zero" |
| : : : "memory"); |
| info |= got_sigill ? 0 : CPUINFO_ZICOND; |
| left &= ~CPUINFO_ZICOND; |
| } |
| |
| sigaction(SIGILL, &sa_old, NULL); |
| assert(left == 0); |
| } |
| |
| info |= CPUINFO_ALWAYS; |
| cpuinfo = info; |
| return info; |
| } |