root/tools/perf/util/perf-regs-arch/perf_regs_powerpc.c
// SPDX-License-Identifier: GPL-2.0

#include <errno.h>
#include <string.h>
#include <regex.h>
#include <linux/zalloc.h>

#include "../debug.h"
#include "../event.h"
#include "../header.h"
#include "../perf_regs.h"
#include "../../perf-sys.h"
#include "../../arch/powerpc/util/utils_header.h"
#include "../../arch/powerpc/include/perf_regs.h"

#include <linux/kernel.h>

#define PVR_POWER9              0x004E
#define PVR_POWER10             0x0080
#define PVR_POWER11             0x0082

/* REG or %rREG */
#define SDT_OP_REGEX1  "^(%r)?([1-2]?[0-9]|3[0-1])$"

/* -NUM(REG) or NUM(REG) or -NUM(%rREG) or NUM(%rREG) */
#define SDT_OP_REGEX2  "^(\\-)?([0-9]+)\\((%r)?([1-2]?[0-9]|3[0-1])\\)$"

static regex_t sdt_op_regex1, sdt_op_regex2;

static int sdt_init_op_regex(void)
{
        static int initialized;
        int ret = 0;

        if (initialized)
                return 0;

        ret = regcomp(&sdt_op_regex1, SDT_OP_REGEX1, REG_EXTENDED);
        if (ret)
                goto error;

        ret = regcomp(&sdt_op_regex2, SDT_OP_REGEX2, REG_EXTENDED);
        if (ret)
                goto free_regex1;

        initialized = 1;
        return 0;

free_regex1:
        regfree(&sdt_op_regex1);
error:
        pr_debug4("Regex compilation error.\n");
        return ret;
}

/*
 * Parse OP and convert it into uprobe format, which is, +/-NUM(%gprREG).
 * Possible variants of OP are:
 *      Format          Example
 *      -------------------------
 *      NUM(REG)        48(18)
 *      -NUM(REG)       -48(18)
 *      NUM(%rREG)      48(%r18)
 *      -NUM(%rREG)     -48(%r18)
 *      REG             18
 *      %rREG           %r18
 *      iNUM            i0
 *      i-NUM           i-1
 *
 * SDT marker arguments on Powerpc uses %rREG form with -mregnames flag
 * and REG form with -mno-regnames. Here REG is general purpose register,
 * which is in 0 to 31 range.
 */
int __perf_sdt_arg_parse_op_powerpc(char *old_op, char **new_op)
{
        int ret, new_len;
        regmatch_t rm[5];
        char prefix;

        /* Constant argument. Uprobe does not support it */
        if (old_op[0] == 'i') {
                pr_debug4("Skipping unsupported SDT argument: %s\n", old_op);
                return SDT_ARG_SKIP;
        }

        ret = sdt_init_op_regex();
        if (ret < 0)
                return ret;

        if (!regexec(&sdt_op_regex1, old_op, 3, rm, 0)) {
                /* REG or %rREG --> %gprREG */

                new_len = 5;    /* % g p r NULL */
                new_len += (int)(rm[2].rm_eo - rm[2].rm_so);

                *new_op = zalloc(new_len);
                if (!*new_op)
                        return -ENOMEM;

                scnprintf(*new_op, new_len, "%%gpr%.*s",
                        (int)(rm[2].rm_eo - rm[2].rm_so), old_op + rm[2].rm_so);
        } else if (!regexec(&sdt_op_regex2, old_op, 5, rm, 0)) {
                /*
                 * -NUM(REG) or NUM(REG) or -NUM(%rREG) or NUM(%rREG) -->
                 *      +/-NUM(%gprREG)
                 */
                prefix = (rm[1].rm_so == -1) ? '+' : '-';

                new_len = 8;    /* +/- ( % g p r ) NULL */
                new_len += (int)(rm[2].rm_eo - rm[2].rm_so);
                new_len += (int)(rm[4].rm_eo - rm[4].rm_so);

                *new_op = zalloc(new_len);
                if (!*new_op)
                        return -ENOMEM;

                scnprintf(*new_op, new_len, "%c%.*s(%%gpr%.*s)", prefix,
                        (int)(rm[2].rm_eo - rm[2].rm_so), old_op + rm[2].rm_so,
                        (int)(rm[4].rm_eo - rm[4].rm_so), old_op + rm[4].rm_so);
        } else {
                pr_debug4("Skipping unsupported SDT argument: %s\n", old_op);
                return SDT_ARG_SKIP;
        }

        return SDT_ARG_VALID;
}

/*
 * mfspr is a POWERPC specific instruction, ensure it's only
 * built and called on POWERPC by guarding with __powerpc64__
 * or __powerpc__.
 */
#if defined(__powerpc64__) && defined(__powerpc__)
uint64_t __perf_reg_mask_powerpc(bool intr)
{
        struct perf_event_attr attr = {
                .type                   = PERF_TYPE_HARDWARE,
                .config                 = PERF_COUNT_HW_CPU_CYCLES,
                .sample_type            = PERF_SAMPLE_REGS_INTR,
                .precise_ip             = 1,
                .disabled               = 1,
                .exclude_kernel         = 1,
        };
        int fd;
        u32 version;
        u64 extended_mask = 0, mask = PERF_REGS_MASK;

        if (!intr)
                return PERF_REGS_MASK;

        /*
         * Get the PVR value to set the extended
         * mask specific to platform.
         */
        version = (((mfspr(SPRN_PVR)) >>  16) & 0xFFFF);
        if (version == PVR_POWER9)
                extended_mask = PERF_REG_PMU_MASK_300;
        else if ((version == PVR_POWER10) || (version == PVR_POWER11))
                extended_mask = PERF_REG_PMU_MASK_31;
        else
                return mask;

        attr.sample_regs_intr = extended_mask;
        attr.sample_period = 1;
        event_attr_init(&attr);

        /*
         * Check if the pmu supports perf extended regs, before
         * returning the register mask to sample. Open the event
         * on the perf process to check this.
         */
        fd = sys_perf_event_open(&attr, /*pid=*/0, /*cpu=*/-1,
                                 /*group_fd=*/-1, /*flags=*/0);
        if (fd != -1) {
                close(fd);
                mask |= extended_mask;
        }
        return mask;
}
#else
uint64_t __perf_reg_mask_powerpc(bool intr __maybe_unused)
{
        return PERF_REGS_MASK;
}
#endif

const char *__perf_reg_name_powerpc(int id)
{
        switch (id) {
        case PERF_REG_POWERPC_R0:
                return "r0";
        case PERF_REG_POWERPC_R1:
                return "r1";
        case PERF_REG_POWERPC_R2:
                return "r2";
        case PERF_REG_POWERPC_R3:
                return "r3";
        case PERF_REG_POWERPC_R4:
                return "r4";
        case PERF_REG_POWERPC_R5:
                return "r5";
        case PERF_REG_POWERPC_R6:
                return "r6";
        case PERF_REG_POWERPC_R7:
                return "r7";
        case PERF_REG_POWERPC_R8:
                return "r8";
        case PERF_REG_POWERPC_R9:
                return "r9";
        case PERF_REG_POWERPC_R10:
                return "r10";
        case PERF_REG_POWERPC_R11:
                return "r11";
        case PERF_REG_POWERPC_R12:
                return "r12";
        case PERF_REG_POWERPC_R13:
                return "r13";
        case PERF_REG_POWERPC_R14:
                return "r14";
        case PERF_REG_POWERPC_R15:
                return "r15";
        case PERF_REG_POWERPC_R16:
                return "r16";
        case PERF_REG_POWERPC_R17:
                return "r17";
        case PERF_REG_POWERPC_R18:
                return "r18";
        case PERF_REG_POWERPC_R19:
                return "r19";
        case PERF_REG_POWERPC_R20:
                return "r20";
        case PERF_REG_POWERPC_R21:
                return "r21";
        case PERF_REG_POWERPC_R22:
                return "r22";
        case PERF_REG_POWERPC_R23:
                return "r23";
        case PERF_REG_POWERPC_R24:
                return "r24";
        case PERF_REG_POWERPC_R25:
                return "r25";
        case PERF_REG_POWERPC_R26:
                return "r26";
        case PERF_REG_POWERPC_R27:
                return "r27";
        case PERF_REG_POWERPC_R28:
                return "r28";
        case PERF_REG_POWERPC_R29:
                return "r29";
        case PERF_REG_POWERPC_R30:
                return "r30";
        case PERF_REG_POWERPC_R31:
                return "r31";
        case PERF_REG_POWERPC_NIP:
                return "nip";
        case PERF_REG_POWERPC_MSR:
                return "msr";
        case PERF_REG_POWERPC_ORIG_R3:
                return "orig_r3";
        case PERF_REG_POWERPC_CTR:
                return "ctr";
        case PERF_REG_POWERPC_LINK:
                return "link";
        case PERF_REG_POWERPC_XER:
                return "xer";
        case PERF_REG_POWERPC_CCR:
                return "ccr";
        case PERF_REG_POWERPC_SOFTE:
                return "softe";
        case PERF_REG_POWERPC_TRAP:
                return "trap";
        case PERF_REG_POWERPC_DAR:
                return "dar";
        case PERF_REG_POWERPC_DSISR:
                return "dsisr";
        case PERF_REG_POWERPC_SIER:
                return "sier";
        case PERF_REG_POWERPC_MMCRA:
                return "mmcra";
        case PERF_REG_POWERPC_MMCR0:
                return "mmcr0";
        case PERF_REG_POWERPC_MMCR1:
                return "mmcr1";
        case PERF_REG_POWERPC_MMCR2:
                return "mmcr2";
        case PERF_REG_POWERPC_MMCR3:
                return "mmcr3";
        case PERF_REG_POWERPC_SIER2:
                return "sier2";
        case PERF_REG_POWERPC_SIER3:
                return "sier3";
        case PERF_REG_POWERPC_PMC1:
                return "pmc1";
        case PERF_REG_POWERPC_PMC2:
                return "pmc2";
        case PERF_REG_POWERPC_PMC3:
                return "pmc3";
        case PERF_REG_POWERPC_PMC4:
                return "pmc4";
        case PERF_REG_POWERPC_PMC5:
                return "pmc5";
        case PERF_REG_POWERPC_PMC6:
                return "pmc6";
        case PERF_REG_POWERPC_SDAR:
                return "sdar";
        case PERF_REG_POWERPC_SIAR:
                return "siar";
        default:
                break;
        }
        return NULL;
}

uint64_t __perf_reg_ip_powerpc(void)
{
        return PERF_REG_POWERPC_NIP;
}

uint64_t __perf_reg_sp_powerpc(void)
{
        return PERF_REG_POWERPC_R1;
}