| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (C) 2019 ARM Limited */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <signal.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <assert.h> |
| #include <sys/auxv.h> |
| #include <linux/auxvec.h> |
| #include <ucontext.h> |
| |
| #include <asm/unistd.h> |
| |
| #include <kselftest.h> |
| |
| #include "test_signals.h" |
| #include "test_signals_utils.h" |
| #include "testcases/testcases.h" |
| |
| |
| extern struct tdescr *current; |
| |
| static int sig_copyctx = SIGTRAP; |
| |
| static char const *const feats_names[FMAX_END] = { |
| " SSBS ", |
| " SVE ", |
| }; |
| |
| #define MAX_FEATS_SZ 128 |
| static char feats_string[MAX_FEATS_SZ]; |
| |
| static inline char *feats_to_string(unsigned long feats) |
| { |
| size_t flen = MAX_FEATS_SZ - 1; |
| |
| for (int i = 0; i < FMAX_END; i++) { |
| if (feats & (1UL << i)) { |
| size_t tlen = strlen(feats_names[i]); |
| |
| assert(flen > tlen); |
| flen -= tlen; |
| strncat(feats_string, feats_names[i], flen); |
| } |
| } |
| |
| return feats_string; |
| } |
| |
| static void unblock_signal(int signum) |
| { |
| sigset_t sset; |
| |
| sigemptyset(&sset); |
| sigaddset(&sset, signum); |
| sigprocmask(SIG_UNBLOCK, &sset, NULL); |
| } |
| |
| static void default_result(struct tdescr *td, bool force_exit) |
| { |
| if (td->result == KSFT_SKIP) { |
| fprintf(stderr, "==>> completed. SKIP.\n"); |
| } else if (td->pass) { |
| fprintf(stderr, "==>> completed. PASS(1)\n"); |
| td->result = KSFT_PASS; |
| } else { |
| fprintf(stdout, "==>> completed. FAIL(0)\n"); |
| td->result = KSFT_FAIL; |
| } |
| |
| if (force_exit) |
| exit(td->result); |
| } |
| |
| /* |
| * The following handle_signal_* helpers are used by main default_handler |
| * and are meant to return true when signal is handled successfully: |
| * when false is returned instead, it means that the signal was somehow |
| * unexpected in that context and it was NOT handled; default_handler will |
| * take care of such unexpected situations. |
| */ |
| |
| static bool handle_signal_unsupported(struct tdescr *td, |
| siginfo_t *si, void *uc) |
| { |
| if (feats_ok(td)) |
| return false; |
| |
| /* Mangling PC to avoid loops on original SIGILL */ |
| ((ucontext_t *)uc)->uc_mcontext.pc += 4; |
| |
| if (!td->initialized) { |
| fprintf(stderr, |
| "Got SIG_UNSUPP @test_init. Ignore.\n"); |
| } else { |
| fprintf(stderr, |
| "-- RX SIG_UNSUPP on unsupported feat...OK\n"); |
| td->pass = 1; |
| default_result(current, 1); |
| } |
| |
| return true; |
| } |
| |
| static bool handle_signal_trigger(struct tdescr *td, |
| siginfo_t *si, void *uc) |
| { |
| td->triggered = 1; |
| /* ->run was asserted NON-NULL in test_setup() already */ |
| td->run(td, si, uc); |
| |
| return true; |
| } |
| |
| static bool handle_signal_ok(struct tdescr *td, |
| siginfo_t *si, void *uc) |
| { |
| /* |
| * it's a bug in the test code when this assert fail: |
| * if sig_trig was defined, it must have been used before getting here. |
| */ |
| assert(!td->sig_trig || td->triggered); |
| fprintf(stderr, |
| "SIG_OK -- SP:0x%llX si_addr@:%p si_code:%d token@:%p offset:%ld\n", |
| ((ucontext_t *)uc)->uc_mcontext.sp, |
| si->si_addr, si->si_code, td->token, td->token - si->si_addr); |
| /* |
| * fake_sigreturn tests, which have sanity_enabled=1, set, at the very |
| * last time, the token field to the SP address used to place the fake |
| * sigframe: so token==0 means we never made it to the end, |
| * segfaulting well-before, and the test is possibly broken. |
| */ |
| if (!td->sanity_disabled && !td->token) { |
| fprintf(stdout, |
| "current->token ZEROED...test is probably broken!\n"); |
| abort(); |
| } |
| /* |
| * Trying to narrow down the SEGV to the ones generated by Kernel itself |
| * via arm64_notify_segfault(). This is a best-effort check anyway, and |
| * the si_code check may need to change if this aspect of the kernel |
| * ABI changes. |
| */ |
| if (td->sig_ok == SIGSEGV && si->si_code != SEGV_ACCERR) { |
| fprintf(stdout, |
| "si_code != SEGV_ACCERR...test is probably broken!\n"); |
| abort(); |
| } |
| td->pass = 1; |
| /* |
| * Some tests can lead to SEGV loops: in such a case we want to |
| * terminate immediately exiting straight away; some others are not |
| * supposed to outlive the signal handler code, due to the content of |
| * the fake sigframe which caused the signal itself. |
| */ |
| default_result(current, 1); |
| |
| return true; |
| } |
| |
| static bool handle_signal_copyctx(struct tdescr *td, |
| siginfo_t *si, void *uc) |
| { |
| /* Mangling PC to avoid loops on original BRK instr */ |
| ((ucontext_t *)uc)->uc_mcontext.pc += 4; |
| memcpy(td->live_uc, uc, td->live_sz); |
| ASSERT_GOOD_CONTEXT(td->live_uc); |
| td->live_uc_valid = 1; |
| fprintf(stderr, |
| "GOOD CONTEXT grabbed from sig_copyctx handler\n"); |
| |
| return true; |
| } |
| |
| static void default_handler(int signum, siginfo_t *si, void *uc) |
| { |
| if (current->sig_unsupp && signum == current->sig_unsupp && |
| handle_signal_unsupported(current, si, uc)) { |
| fprintf(stderr, "Handled SIG_UNSUPP\n"); |
| } else if (current->sig_trig && signum == current->sig_trig && |
| handle_signal_trigger(current, si, uc)) { |
| fprintf(stderr, "Handled SIG_TRIG\n"); |
| } else if (current->sig_ok && signum == current->sig_ok && |
| handle_signal_ok(current, si, uc)) { |
| fprintf(stderr, "Handled SIG_OK\n"); |
| } else if (signum == sig_copyctx && current->live_uc && |
| handle_signal_copyctx(current, si, uc)) { |
| fprintf(stderr, "Handled SIG_COPYCTX\n"); |
| } else { |
| if (signum == SIGALRM && current->timeout) { |
| fprintf(stderr, "-- Timeout !\n"); |
| } else { |
| fprintf(stderr, |
| "-- RX UNEXPECTED SIGNAL: %d\n", signum); |
| } |
| default_result(current, 1); |
| } |
| } |
| |
| static int default_setup(struct tdescr *td) |
| { |
| struct sigaction sa; |
| |
| sa.sa_sigaction = default_handler; |
| sa.sa_flags = SA_SIGINFO | SA_RESTART; |
| sa.sa_flags |= td->sa_flags; |
| sigemptyset(&sa.sa_mask); |
| /* uncatchable signals naturally skipped ... */ |
| for (int sig = 1; sig < 32; sig++) |
| sigaction(sig, &sa, NULL); |
| /* |
| * RT Signals default disposition is Term but they cannot be |
| * generated by the Kernel in response to our tests; so just catch |
| * them all and report them as UNEXPECTED signals. |
| */ |
| for (int sig = SIGRTMIN; sig <= SIGRTMAX; sig++) |
| sigaction(sig, &sa, NULL); |
| |
| /* just in case...unblock explicitly all we need */ |
| if (td->sig_trig) |
| unblock_signal(td->sig_trig); |
| if (td->sig_ok) |
| unblock_signal(td->sig_ok); |
| if (td->sig_unsupp) |
| unblock_signal(td->sig_unsupp); |
| |
| if (td->timeout) { |
| unblock_signal(SIGALRM); |
| alarm(td->timeout); |
| } |
| fprintf(stderr, "Registered handlers for all signals.\n"); |
| |
| return 1; |
| } |
| |
| static inline int default_trigger(struct tdescr *td) |
| { |
| return !raise(td->sig_trig); |
| } |
| |
| int test_init(struct tdescr *td) |
| { |
| if (td->sig_trig == sig_copyctx) { |
| fprintf(stdout, |
| "Signal %d is RESERVED, cannot be used as a trigger. Aborting\n", |
| sig_copyctx); |
| return 0; |
| } |
| /* just in case */ |
| unblock_signal(sig_copyctx); |
| |
| td->minsigstksz = getauxval(AT_MINSIGSTKSZ); |
| if (!td->minsigstksz) |
| td->minsigstksz = MINSIGSTKSZ; |
| fprintf(stderr, "Detected MINSTKSIGSZ:%d\n", td->minsigstksz); |
| |
| if (td->feats_required) { |
| td->feats_supported = 0; |
| /* |
| * Checking for CPU required features using both the |
| * auxval and the arm64 MRS Emulation to read sysregs. |
| */ |
| if (getauxval(AT_HWCAP) & HWCAP_SSBS) |
| td->feats_supported |= FEAT_SSBS; |
| if (getauxval(AT_HWCAP) & HWCAP_SVE) |
| td->feats_supported |= FEAT_SVE; |
| if (feats_ok(td)) { |
| fprintf(stderr, |
| "Required Features: [%s] supported\n", |
| feats_to_string(td->feats_required & |
| td->feats_supported)); |
| } else { |
| fprintf(stderr, |
| "Required Features: [%s] NOT supported\n", |
| feats_to_string(td->feats_required & |
| ~td->feats_supported)); |
| td->result = KSFT_SKIP; |
| return 0; |
| } |
| } |
| |
| /* Perform test specific additional initialization */ |
| if (td->init && !td->init(td)) { |
| fprintf(stderr, "FAILED Testcase initialization.\n"); |
| return 0; |
| } |
| td->initialized = 1; |
| fprintf(stderr, "Testcase initialized.\n"); |
| |
| return 1; |
| } |
| |
| int test_setup(struct tdescr *td) |
| { |
| /* assert core invariants symptom of a rotten testcase */ |
| assert(current); |
| assert(td); |
| assert(td->name); |
| assert(td->run); |
| |
| /* Default result is FAIL if test setup fails */ |
| td->result = KSFT_FAIL; |
| if (td->setup) |
| return td->setup(td); |
| else |
| return default_setup(td); |
| } |
| |
| int test_run(struct tdescr *td) |
| { |
| if (td->sig_trig) { |
| if (td->trigger) |
| return td->trigger(td); |
| else |
| return default_trigger(td); |
| } else { |
| return td->run(td, NULL, NULL); |
| } |
| } |
| |
| void test_result(struct tdescr *td) |
| { |
| if (td->initialized && td->result != KSFT_SKIP && td->check_result) |
| td->check_result(td); |
| default_result(td, 0); |
| } |
| |
| void test_cleanup(struct tdescr *td) |
| { |
| if (td->cleanup) |
| td->cleanup(td); |
| } |