| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2022 ARM Limited. |
| */ |
| |
| #include <errno.h> |
| #include <signal.h> |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/auxv.h> |
| #include <sys/prctl.h> |
| #include <asm/hwcap.h> |
| #include <asm/sigcontext.h> |
| #include <asm/unistd.h> |
| |
| #include "../../kselftest.h" |
| |
| #define TESTS_PER_HWCAP 2 |
| |
| /* |
| * Function expected to generate SIGILL when the feature is not |
| * supported and return when it is supported. If SIGILL is generated |
| * then the handler must be able to skip over the instruction safely. |
| * |
| * Note that it is expected that for many architecture extensions |
| * there are no specific traps due to no architecture state being |
| * added so we may not fault if running on a kernel which doesn't know |
| * to add the hwcap. |
| */ |
| typedef void (*sigill_fn)(void); |
| |
| static void rng_sigill(void) |
| { |
| asm volatile("mrs x0, S3_3_C2_C4_0" : : : "x0"); |
| } |
| |
| static void sme_sigill(void) |
| { |
| /* RDSVL x0, #0 */ |
| asm volatile(".inst 0x04bf5800" : : : "x0"); |
| } |
| |
| static void sve_sigill(void) |
| { |
| /* RDVL x0, #0 */ |
| asm volatile(".inst 0x04bf5000" : : : "x0"); |
| } |
| |
| static void sve2_sigill(void) |
| { |
| /* SQABS Z0.b, P0/M, Z0.B */ |
| asm volatile(".inst 0x4408A000" : : : "z0"); |
| } |
| |
| static void sveaes_sigill(void) |
| { |
| /* AESD z0.b, z0.b, z0.b */ |
| asm volatile(".inst 0x4522e400" : : : "z0"); |
| } |
| |
| static void svepmull_sigill(void) |
| { |
| /* PMULLB Z0.Q, Z0.D, Z0.D */ |
| asm volatile(".inst 0x45006800" : : : "z0"); |
| } |
| |
| static void svebitperm_sigill(void) |
| { |
| /* BDEP Z0.B, Z0.B, Z0.B */ |
| asm volatile(".inst 0x4500b400" : : : "z0"); |
| } |
| |
| static void svesha3_sigill(void) |
| { |
| /* EOR3 Z0.D, Z0.D, Z0.D, Z0.D */ |
| asm volatile(".inst 0x4203800" : : : "z0"); |
| } |
| |
| static void svesm4_sigill(void) |
| { |
| /* SM4E Z0.S, Z0.S, Z0.S */ |
| asm volatile(".inst 0x4523e000" : : : "z0"); |
| } |
| |
| static void svei8mm_sigill(void) |
| { |
| /* USDOT Z0.S, Z0.B, Z0.B[0] */ |
| asm volatile(".inst 0x44a01800" : : : "z0"); |
| } |
| |
| static void svef32mm_sigill(void) |
| { |
| /* FMMLA Z0.S, Z0.S, Z0.S */ |
| asm volatile(".inst 0x64a0e400" : : : "z0"); |
| } |
| |
| static void svef64mm_sigill(void) |
| { |
| /* FMMLA Z0.D, Z0.D, Z0.D */ |
| asm volatile(".inst 0x64e0e400" : : : "z0"); |
| } |
| |
| static void svebf16_sigill(void) |
| { |
| /* BFCVT Z0.H, P0/M, Z0.S */ |
| asm volatile(".inst 0x658aa000" : : : "z0"); |
| } |
| |
| static const struct hwcap_data { |
| const char *name; |
| unsigned long at_hwcap; |
| unsigned long hwcap_bit; |
| const char *cpuinfo; |
| sigill_fn sigill_fn; |
| bool sigill_reliable; |
| } hwcaps[] = { |
| { |
| .name = "RNG", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_RNG, |
| .cpuinfo = "rng", |
| .sigill_fn = rng_sigill, |
| }, |
| { |
| .name = "SME", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SME, |
| .cpuinfo = "sme", |
| .sigill_fn = sme_sigill, |
| .sigill_reliable = true, |
| }, |
| { |
| .name = "SVE", |
| .at_hwcap = AT_HWCAP, |
| .hwcap_bit = HWCAP_SVE, |
| .cpuinfo = "sve", |
| .sigill_fn = sve_sigill, |
| .sigill_reliable = true, |
| }, |
| { |
| .name = "SVE 2", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVE2, |
| .cpuinfo = "sve2", |
| .sigill_fn = sve2_sigill, |
| }, |
| { |
| .name = "SVE AES", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVEAES, |
| .cpuinfo = "sveaes", |
| .sigill_fn = sveaes_sigill, |
| }, |
| { |
| .name = "SVE2 PMULL", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVEPMULL, |
| .cpuinfo = "svepmull", |
| .sigill_fn = svepmull_sigill, |
| }, |
| { |
| .name = "SVE2 BITPERM", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVEBITPERM, |
| .cpuinfo = "svebitperm", |
| .sigill_fn = svebitperm_sigill, |
| }, |
| { |
| .name = "SVE2 SHA3", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVESHA3, |
| .cpuinfo = "svesha3", |
| .sigill_fn = svesha3_sigill, |
| }, |
| { |
| .name = "SVE2 SM4", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVESM4, |
| .cpuinfo = "svesm4", |
| .sigill_fn = svesm4_sigill, |
| }, |
| { |
| .name = "SVE2 I8MM", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVEI8MM, |
| .cpuinfo = "svei8mm", |
| .sigill_fn = svei8mm_sigill, |
| }, |
| { |
| .name = "SVE2 F32MM", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVEF32MM, |
| .cpuinfo = "svef32mm", |
| .sigill_fn = svef32mm_sigill, |
| }, |
| { |
| .name = "SVE2 F64MM", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVEF64MM, |
| .cpuinfo = "svef64mm", |
| .sigill_fn = svef64mm_sigill, |
| }, |
| { |
| .name = "SVE2 BF16", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVEBF16, |
| .cpuinfo = "svebf16", |
| .sigill_fn = svebf16_sigill, |
| }, |
| { |
| .name = "SVE2 EBF16", |
| .at_hwcap = AT_HWCAP2, |
| .hwcap_bit = HWCAP2_SVE_EBF16, |
| .cpuinfo = "sveebf16", |
| }, |
| }; |
| |
| static bool seen_sigill; |
| |
| static void handle_sigill(int sig, siginfo_t *info, void *context) |
| { |
| ucontext_t *uc = context; |
| |
| seen_sigill = true; |
| |
| /* Skip over the offending instruction */ |
| uc->uc_mcontext.pc += 4; |
| } |
| |
| bool cpuinfo_present(const char *name) |
| { |
| FILE *f; |
| char buf[2048], name_space[30], name_newline[30]; |
| char *s; |
| |
| /* |
| * The feature should appear with a leading space and either a |
| * trailing space or a newline. |
| */ |
| snprintf(name_space, sizeof(name_space), " %s ", name); |
| snprintf(name_newline, sizeof(name_newline), " %s\n", name); |
| |
| f = fopen("/proc/cpuinfo", "r"); |
| if (!f) { |
| ksft_print_msg("Failed to open /proc/cpuinfo\n"); |
| return false; |
| } |
| |
| while (fgets(buf, sizeof(buf), f)) { |
| /* Features: line? */ |
| if (strncmp(buf, "Features\t:", strlen("Features\t:")) != 0) |
| continue; |
| |
| /* All CPUs should be symmetric, don't read any more */ |
| fclose(f); |
| |
| s = strstr(buf, name_space); |
| if (s) |
| return true; |
| s = strstr(buf, name_newline); |
| if (s) |
| return true; |
| |
| return false; |
| } |
| |
| ksft_print_msg("Failed to find Features in /proc/cpuinfo\n"); |
| fclose(f); |
| return false; |
| } |
| |
| int main(void) |
| { |
| const struct hwcap_data *hwcap; |
| int i, ret; |
| bool have_cpuinfo, have_hwcap; |
| struct sigaction sa; |
| |
| ksft_print_header(); |
| ksft_set_plan(ARRAY_SIZE(hwcaps) * TESTS_PER_HWCAP); |
| |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_sigaction = handle_sigill; |
| sa.sa_flags = SA_RESTART | SA_SIGINFO; |
| sigemptyset(&sa.sa_mask); |
| ret = sigaction(SIGILL, &sa, NULL); |
| if (ret < 0) |
| ksft_exit_fail_msg("Failed to install SIGILL handler: %s (%d)\n", |
| strerror(errno), errno); |
| |
| for (i = 0; i < ARRAY_SIZE(hwcaps); i++) { |
| hwcap = &hwcaps[i]; |
| |
| have_hwcap = getauxval(hwcap->at_hwcap) & hwcap->hwcap_bit; |
| have_cpuinfo = cpuinfo_present(hwcap->cpuinfo); |
| |
| if (have_hwcap) |
| ksft_print_msg("%s present\n", hwcap->name); |
| |
| ksft_test_result(have_hwcap == have_cpuinfo, |
| "cpuinfo_match_%s\n", hwcap->name); |
| |
| if (hwcap->sigill_fn) { |
| seen_sigill = false; |
| hwcap->sigill_fn(); |
| |
| if (have_hwcap) { |
| /* Should be able to use the extension */ |
| ksft_test_result(!seen_sigill, "sigill_%s\n", |
| hwcap->name); |
| } else if (hwcap->sigill_reliable) { |
| /* Guaranteed a SIGILL */ |
| ksft_test_result(seen_sigill, "sigill_%s\n", |
| hwcap->name); |
| } else { |
| /* Missing SIGILL might be fine */ |
| ksft_print_msg("SIGILL %sreported for %s\n", |
| seen_sigill ? "" : "not ", |
| hwcap->name); |
| ksft_test_result_skip("sigill_%s\n", |
| hwcap->name); |
| } |
| } else { |
| ksft_test_result_skip("sigill_%s\n", |
| hwcap->name); |
| } |
| } |
| |
| ksft_print_cnts(); |
| |
| return 0; |
| } |