// 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 3 /* * Function expected to generate exception when the feature is not * supported and return when it is supported. If the specific exception * 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 (*sig_fn)(void); static void aes_sigill(void) { /* AESE V0.16B, V0.16B */ asm volatile(".inst 0x4e284800" : : : ); } static void atomics_sigill(void) { /* STADD W0, [SP] */ asm volatile(".inst 0xb82003ff" : : : ); } static void crc32_sigill(void) { /* CRC32W W0, W0, W1 */ asm volatile(".inst 0x1ac14800" : : : ); } static void cssc_sigill(void) { /* CNT x0, x0 */ asm volatile(".inst 0xdac01c00" : : : "x0"); } static void fp_sigill(void) { asm volatile("fmov s0, #1"); } static void ilrcpc_sigill(void) { /* LDAPUR W0, [SP, #8] */ asm volatile(".inst 0x994083e0" : : : ); } static void jscvt_sigill(void) { /* FJCVTZS W0, D0 */ asm volatile(".inst 0x1e7e0000" : : : ); } static void lrcpc_sigill(void) { /* LDAPR W0, [SP, #0] */ asm volatile(".inst 0xb8bfc3e0" : : : ); } static void mops_sigill(void) { char dst[1], src[1]; register char *dstp asm ("x0") = dst; register char *srcp asm ("x1") = src; register long size asm ("x2") = 1; /* CPYP [x0]!, [x1]!, x2! */ asm volatile(".inst 0x1d010440" : "+r" (dstp), "+r" (srcp), "+r" (size) : : "cc", "memory"); } static void pmull_sigill(void) { /* PMULL V0.1Q, V0.1D, V0.1D */ asm volatile(".inst 0x0ee0e000" : : : ); } static void rng_sigill(void) { asm volatile("mrs x0, S3_3_C2_C4_0" : : : "x0"); } static void sha1_sigill(void) { /* SHA1H S0, S0 */ asm volatile(".inst 0x5e280800" : : : ); } static void sha2_sigill(void) { /* SHA256H Q0, Q0, V0.4S */ asm volatile(".inst 0x5e004000" : : : ); } static void sha512_sigill(void) { /* SHA512H Q0, Q0, V0.2D */ asm volatile(".inst 0xce608000" : : : ); } static void sme_sigill(void) { /* RDSVL x0, #0 */ asm volatile(".inst 0x04bf5800" : : : "x0"); } static void sme2_sigill(void) { /* SMSTART ZA */ asm volatile("msr S0_3_C4_C5_3, xzr" : : : ); /* ZERO ZT0 */ asm volatile(".inst 0xc0480001" : : : ); /* SMSTOP */ asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); } static void sme2p1_sigill(void) { /* SMSTART SM */ asm volatile("msr S0_3_C4_C3_3, xzr" : : : ); /* BFCLAMP { Z0.H - Z1.H }, Z0.H, Z0.H */ asm volatile(".inst 0xc120C000" : : : ); /* SMSTOP */ asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); } static void smei16i32_sigill(void) { /* SMSTART */ asm volatile("msr S0_3_C4_C7_3, xzr" : : : ); /* SMOPA ZA0.S, P0/M, P0/M, Z0.B, Z0.B */ asm volatile(".inst 0xa0800000" : : : ); /* SMSTOP */ asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); } static void smebi32i32_sigill(void) { /* SMSTART */ asm volatile("msr S0_3_C4_C7_3, xzr" : : : ); /* BMOPA ZA0.S, P0/M, P0/M, Z0.B, Z0.B */ asm volatile(".inst 0x80800008" : : : ); /* SMSTOP */ asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); } static void smeb16b16_sigill(void) { /* SMSTART */ asm volatile("msr S0_3_C4_C7_3, xzr" : : : ); /* BFADD ZA.H[W0, 0], {Z0.H-Z1.H} */ asm volatile(".inst 0xC1E41C00" : : : ); /* SMSTOP */ asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); } static void smef16f16_sigill(void) { /* SMSTART */ asm volatile("msr S0_3_C4_C7_3, xzr" : : : ); /* FADD ZA.H[W0, 0], { Z0.H-Z1.H } */ asm volatile(".inst 0xc1a41C00" : : : ); /* SMSTOP */ asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); } 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 sve2p1_sigill(void) { /* BFADD Z0.H, Z0.H, Z0.H */ asm volatile(".inst 0x65000000" : : : "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 void hbc_sigill(void) { /* BC.EQ +4 */ asm volatile("cmp xzr, xzr\n" ".inst 0x54000030" : : : "cc"); } static void uscat_sigbus(void) { /* unaligned atomic access */ asm volatile("ADD x1, sp, #2" : : : ); /* STADD W0, [X1] */ asm volatile(".inst 0xb820003f" : : : ); } static const struct hwcap_data { const char *name; unsigned long at_hwcap; unsigned long hwcap_bit; const char *cpuinfo; sig_fn sigill_fn; bool sigill_reliable; sig_fn sigbus_fn; bool sigbus_reliable; } hwcaps[] = { { .name = "AES", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_AES, .cpuinfo = "aes", .sigill_fn = aes_sigill, }, { .name = "CRC32", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_CRC32, .cpuinfo = "crc32", .sigill_fn = crc32_sigill, }, { .name = "CSSC", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_CSSC, .cpuinfo = "cssc", .sigill_fn = cssc_sigill, }, { .name = "FP", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_FP, .cpuinfo = "fp", .sigill_fn = fp_sigill, }, { .name = "JSCVT", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_JSCVT, .cpuinfo = "jscvt", .sigill_fn = jscvt_sigill, }, { .name = "LRCPC", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_LRCPC, .cpuinfo = "lrcpc", .sigill_fn = lrcpc_sigill, }, { .name = "LRCPC2", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_ILRCPC, .cpuinfo = "ilrcpc", .sigill_fn = ilrcpc_sigill, }, { .name = "LSE", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_ATOMICS, .cpuinfo = "atomics", .sigill_fn = atomics_sigill, }, { .name = "LSE2", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_USCAT, .cpuinfo = "uscat", .sigill_fn = atomics_sigill, .sigbus_fn = uscat_sigbus, .sigbus_reliable = true, }, { .name = "MOPS", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_MOPS, .cpuinfo = "mops", .sigill_fn = mops_sigill, .sigill_reliable = true, }, { .name = "PMULL", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_PMULL, .cpuinfo = "pmull", .sigill_fn = pmull_sigill, }, { .name = "RNG", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_RNG, .cpuinfo = "rng", .sigill_fn = rng_sigill, }, { .name = "RPRFM", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_RPRFM, .cpuinfo = "rprfm", }, { .name = "SHA1", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_SHA1, .cpuinfo = "sha1", .sigill_fn = sha1_sigill, }, { .name = "SHA2", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_SHA2, .cpuinfo = "sha2", .sigill_fn = sha2_sigill, }, { .name = "SHA512", .at_hwcap = AT_HWCAP, .hwcap_bit = HWCAP_SHA512, .cpuinfo = "sha512", .sigill_fn = sha512_sigill, }, { .name = "SME", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_SME, .cpuinfo = "sme", .sigill_fn = sme_sigill, .sigill_reliable = true, }, { .name = "SME2", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_SME2, .cpuinfo = "sme2", .sigill_fn = sme2_sigill, .sigill_reliable = true, }, { .name = "SME 2.1", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_SME2P1, .cpuinfo = "sme2p1", .sigill_fn = sme2p1_sigill, }, { .name = "SME I16I32", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_SME_I16I32, .cpuinfo = "smei16i32", .sigill_fn = smei16i32_sigill, }, { .name = "SME BI32I32", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_SME_BI32I32, .cpuinfo = "smebi32i32", .sigill_fn = smebi32i32_sigill, }, { .name = "SME B16B16", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_SME_B16B16, .cpuinfo = "smeb16b16", .sigill_fn = smeb16b16_sigill, }, { .name = "SME F16F16", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_SME_F16F16, .cpuinfo = "smef16f16", .sigill_fn = smef16f16_sigill, }, { .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 2.1", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_SVE2P1, .cpuinfo = "sve2p1", .sigill_fn = sve2p1_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", }, { .name = "HBC", .at_hwcap = AT_HWCAP2, .hwcap_bit = HWCAP2_HBC, .cpuinfo = "hbc", .sigill_fn = hbc_sigill, .sigill_reliable = true, }, }; typedef void (*sighandler_fn)(int, siginfo_t *, void *); #define DEF_SIGHANDLER_FUNC(SIG, NUM) \ static bool seen_##SIG; \ static void handle_##SIG(int sig, siginfo_t *info, void *context) \ { \ ucontext_t *uc = context; \ \ seen_##SIG = true; \ /* Skip over the offending instruction */ \ uc->uc_mcontext.pc += 4; \ } DEF_SIGHANDLER_FUNC(sigill, SIGILL); DEF_SIGHANDLER_FUNC(sigbus, SIGBUS); 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; } static int install_sigaction(int signum, sighandler_fn handler) { int ret; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = handler; sa.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&sa.sa_mask); ret = sigaction(signum, &sa, NULL); if (ret < 0) ksft_exit_fail_msg("Failed to install SIGNAL handler: %s (%d)\n", strerror(errno), errno); return ret; } static void uninstall_sigaction(int signum) { if (sigaction(signum, NULL, NULL) < 0) ksft_exit_fail_msg("Failed to uninstall SIGNAL handler: %s (%d)\n", strerror(errno), errno); } #define DEF_INST_RAISE_SIG(SIG, NUM) \ static bool inst_raise_##SIG(const struct hwcap_data *hwcap, \ bool have_hwcap) \ { \ if (!hwcap->SIG##_fn) { \ ksft_test_result_skip(#SIG"_%s\n", hwcap->name); \ /* assume that it would raise exception in default */ \ return true; \ } \ \ install_sigaction(NUM, handle_##SIG); \ \ seen_##SIG = false; \ hwcap->SIG##_fn(); \ \ if (have_hwcap) { \ /* Should be able to use the extension */ \ ksft_test_result(!seen_##SIG, \ #SIG"_%s\n", hwcap->name); \ } else if (hwcap->SIG##_reliable) { \ /* Guaranteed a SIGNAL */ \ ksft_test_result(seen_##SIG, \ #SIG"_%s\n", hwcap->name); \ } else { \ /* Missing SIGNAL might be fine */ \ ksft_print_msg(#SIG"_%sreported for %s\n", \ seen_##SIG ? "" : "not ", \ hwcap->name); \ ksft_test_result_skip(#SIG"_%s\n", \ hwcap->name); \ } \ \ uninstall_sigaction(NUM); \ return seen_##SIG; \ } DEF_INST_RAISE_SIG(sigill, SIGILL); DEF_INST_RAISE_SIG(sigbus, SIGBUS); int main(void) { int i; const struct hwcap_data *hwcap; bool have_cpuinfo, have_hwcap, raise_sigill; ksft_print_header(); ksft_set_plan(ARRAY_SIZE(hwcaps) * TESTS_PER_HWCAP); 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); /* * Testing for SIGBUS only makes sense after make sure * that the instruction does not cause a SIGILL signal. */ raise_sigill = inst_raise_sigill(hwcap, have_hwcap); if (!raise_sigill) inst_raise_sigbus(hwcap, have_hwcap); else ksft_test_result_skip("sigbus_%s\n", hwcap->name); } ksft_print_cnts(); return 0; }