root/tools/testing/selftests/arm64/signal/test_signals_utils.c
// 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 ",
        " SME ",
        " FA64 ",
        " SME2 ",
        " GCS ",
};

#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;

        feats_string[0] = '\0';

        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();
        }
        if (td->sig_ok_code) {
                if (si->si_code != td->sig_ok_code) {
                        fprintf(stdout, "si_code is %d not %d\n",
                                si->si_code, td->sig_ok_code);
                        abort();
                }
        } else {
                /*
                 * 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_in)
{
        ucontext_t *uc = uc_in;
        struct _aarch64_ctx *head;
        struct extra_context *extra, *copied_extra;
        size_t offset = 0;
        size_t to_copy;

        ASSERT_GOOD_CONTEXT(uc);

        /* Mangling PC to avoid loops on original BRK instr */
        uc->uc_mcontext.pc += 4;

        /*
         * Check for an preserve any extra data too with fixups.
         */
        head = (struct _aarch64_ctx *)uc->uc_mcontext.__reserved;
        head = get_header(head, EXTRA_MAGIC, td->live_sz, &offset);
        if (head) {
                extra = (struct extra_context *)head;

                /*
                 * The extra buffer must be immediately after the
                 * extra_context and a 16 byte terminator. Include it
                 * in the copy, this was previously validated in
                 * ASSERT_GOOD_CONTEXT().
                 */
                to_copy = __builtin_offsetof(ucontext_t,
                                             uc_mcontext.__reserved);
                to_copy += offset + sizeof(struct extra_context) + 16;
                to_copy += extra->size;
                copied_extra = (struct extra_context *)&(td->live_uc->uc_mcontext.__reserved[offset]);
        } else {
                copied_extra = NULL;
                to_copy = sizeof(ucontext_t);
        }

        if (to_copy > td->live_sz) {
                fprintf(stderr,
                        "Not enough space to grab context, %lu/%lu bytes\n",
                        td->live_sz, to_copy);
                return false;
        }

        memcpy(td->live_uc, uc, to_copy);

        /*
         * If there was any EXTRA_CONTEXT fix up the size to be the
         * struct extra_context and the following terminator record,
         * this means that the rest of the code does not need to have
         * special handling for the record and we don't need to fix up
         * datap for the new location.
         */
        if (copied_extra)
                copied_extra->head.size = sizeof(*copied_extra) + 16;

        td->live_uc_valid = 1;
        fprintf(stderr,
                "%lu byte GOOD CONTEXT grabbed from sig_copyctx handler\n",
                to_copy);

        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 code %d address %p\n",
                                signum, si->si_code, si->si_addr);
                }
                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_incompatible) {
                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 (getauxval(AT_HWCAP2) & HWCAP2_SME)
                        td->feats_supported |= FEAT_SME;
                if (getauxval(AT_HWCAP2) & HWCAP2_SME_FA64)
                        td->feats_supported |= FEAT_SME_FA64;
                if (getauxval(AT_HWCAP2) & HWCAP2_SME2)
                        td->feats_supported |= FEAT_SME2;
                if (getauxval(AT_HWCAP) & HWCAP_GCS)
                        td->feats_supported |= FEAT_GCS;
                if (feats_ok(td)) {
                        if (td->feats_required & td->feats_supported)
                                fprintf(stderr,
                                        "Required Features: [%s] supported\n",
                                        feats_to_string(td->feats_required &
                                                        td->feats_supported));
                        if (!(td->feats_incompatible & td->feats_supported))
                                fprintf(stderr,
                                        "Incompatible Features: [%s] absent\n",
                                        feats_to_string(td->feats_incompatible));
                } else {
                        if ((td->feats_required & td->feats_supported) !=
                            td->feats_supported)
                                fprintf(stderr,
                                        "Required Features: [%s] NOT supported\n",
                                        feats_to_string(td->feats_required &
                                                        ~td->feats_supported));
                        if (td->feats_incompatible & td->feats_supported)
                                fprintf(stderr,
                                        "Incompatible Features: [%s] supported\n",
                                        feats_to_string(td->feats_incompatible &
                                                        ~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->trigger)
                return td->trigger(td);
        else if (td->sig_trig)
                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);
}