root/tools/testing/selftests/powerpc/pmu/sampling_tests/misc.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2022, Athira Rajeev, IBM Corp.
 * Copyright 2022, Madhavan Srinivasan, IBM Corp.
 * Copyright 2022, Kajol Jain, IBM Corp.
 */

#include <unistd.h>
#include <sys/syscall.h>
#include <string.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <ctype.h>

#include "misc.h"

#define PAGE_SIZE               sysconf(_SC_PAGESIZE)

/* Storage for platform version */
int pvr;
u64 platform_extended_mask;

/* Mask and Shift for Event code fields */
int ev_mask_pmcxsel, ev_shift_pmcxsel;          //pmcxsel field
int ev_mask_marked, ev_shift_marked;            //marked filed
int ev_mask_comb, ev_shift_comb;                //combine field
int ev_mask_unit, ev_shift_unit;                //unit field
int ev_mask_pmc, ev_shift_pmc;                  //pmc field
int ev_mask_cache, ev_shift_cache;              //Cache sel field
int ev_mask_sample, ev_shift_sample;            //Random sampling field
int ev_mask_thd_sel, ev_shift_thd_sel;          //thresh_sel field
int ev_mask_thd_start, ev_shift_thd_start;      //thresh_start field
int ev_mask_thd_stop, ev_shift_thd_stop;        //thresh_stop field
int ev_mask_thd_cmp, ev_shift_thd_cmp;          //thresh cmp field
int ev_mask_sm, ev_shift_sm;                    //SDAR mode field
int ev_mask_rsq, ev_shift_rsq;                  //radix scope qual field
int ev_mask_l2l3, ev_shift_l2l3;                //l2l3 sel field
int ev_mask_mmcr3_src, ev_shift_mmcr3_src;      //mmcr3 field

static void init_ev_encodes(void)
{
        ev_mask_pmcxsel = 0xff;
        ev_shift_pmcxsel = 0;
        ev_mask_marked = 1;
        ev_shift_marked = 8;
        ev_mask_unit = 0xf;
        ev_shift_unit = 12;
        ev_mask_pmc = 0xf;
        ev_shift_pmc = 16;
        ev_mask_sample  = 0x1f;
        ev_shift_sample = 24;
        ev_mask_thd_sel = 0x7;
        ev_shift_thd_sel = 29;
        ev_mask_thd_start = 0xf;
        ev_shift_thd_start = 36;
        ev_mask_thd_stop = 0xf;
        ev_shift_thd_stop = 32;

        switch (pvr) {
        case POWER11:
        case POWER10:
                ev_mask_thd_cmp = 0x3ffff;
                ev_shift_thd_cmp = 0;
                ev_mask_rsq = 1;
                ev_shift_rsq = 9;
                ev_mask_comb = 3;
                ev_shift_comb = 10;
                ev_mask_cache = 3;
                ev_shift_cache = 20;
                ev_mask_sm = 0x3;
                ev_shift_sm = 22;
                ev_mask_l2l3 = 0x1f;
                ev_shift_l2l3 = 40;
                ev_mask_mmcr3_src = 0x7fff;
                ev_shift_mmcr3_src = 45;
                break;
        case POWER9:
                ev_mask_comb = 3;
                ev_shift_comb = 10;
                ev_mask_cache = 0xf;
                ev_shift_cache = 20;
                ev_mask_thd_cmp = 0x3ff;
                ev_shift_thd_cmp = 40;
                ev_mask_sm = 0x3;
                ev_shift_sm = 50;
                break;
        default:
                FAIL_IF_EXIT(1);
        }
}

/* Return the extended regs mask value */
u64 perf_get_platform_reg_mask(void)
{
        if (have_hwcap2(PPC_FEATURE2_ARCH_3_1))
                return PERF_POWER10_MASK;
        if (have_hwcap2(PPC_FEATURE2_ARCH_3_00))
                return PERF_POWER9_MASK;

        return -1;
}

int check_extended_regs_support(void)
{
        int fd;
        struct event event;

        event_init(&event, 0x1001e);

        event.attr.type = 4;
        event.attr.sample_period = 1;
        event.attr.disabled = 1;
        event.attr.sample_type = PERF_SAMPLE_REGS_INTR;
        event.attr.sample_regs_intr = platform_extended_mask;

        fd = event_open(&event);
        if (fd != -1)
                return 0;

        return -1;
}

int platform_check_for_tests(void)
{
        pvr = PVR_VER(mfspr(SPRN_PVR));

        /*
         * Check for supported platforms
         * for sampling test
         */
        switch (pvr) {
        case POWER11:
        case POWER10:
        case POWER9:
                break;
        default:
                goto out;
        }

        /*
         * Check PMU driver registered by looking for
         * PPC_FEATURE2_EBB bit in AT_HWCAP2
         */
        if (!have_hwcap2(PPC_FEATURE2_EBB) || !have_hwcap2(PPC_FEATURE2_ARCH_3_00))
                goto out;

        return 0;

out:
        printf("%s: Tests unsupported for this platform\n", __func__);
        return -1;
}

int check_pvr_for_sampling_tests(void)
{
        SKIP_IF(platform_check_for_tests());

        platform_extended_mask = perf_get_platform_reg_mask();
        /* check if platform supports extended regs */
        if (check_extended_regs_support())
                goto out;

        init_ev_encodes();
        return 0;

out:
        printf("%s: Sampling tests un-supported\n", __func__);
        return -1;
}

/*
 * Allocate mmap buffer of "mmap_pages" number of
 * pages.
 */
void *event_sample_buf_mmap(int fd, int mmap_pages)
{
        size_t page_size = sysconf(_SC_PAGESIZE);
        size_t mmap_size;
        void *buff;

        if (mmap_pages <= 0)
                return NULL;

        if (fd <= 0)
                return NULL;

        mmap_size =  page_size * (1 + mmap_pages);
        buff = mmap(NULL, mmap_size,
                PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

        if (buff == MAP_FAILED) {
                perror("mmap() failed.");
                return NULL;
        }
        return buff;
}

/*
 * Post process the mmap buffer.
 * - If sample_count != NULL then return count of total
 *   number of samples present in the mmap buffer.
 * - If sample_count == NULL then return the address
 *   of first sample from the mmap buffer
 */
void *__event_read_samples(void *sample_buff, size_t *size, u64 *sample_count)
{
        size_t page_size = sysconf(_SC_PAGESIZE);
        struct perf_event_header *header = sample_buff + page_size;
        struct perf_event_mmap_page *metadata_page = sample_buff;
        unsigned long data_head, data_tail;

        /*
         * PERF_RECORD_SAMPLE:
         * struct {
         *     struct perf_event_header hdr;
         *     u64 data[];
         * };
         */

        data_head = metadata_page->data_head;
        /* sync memory before reading sample */
        mb();
        data_tail = metadata_page->data_tail;

        /* Check for sample_count */
        if (sample_count)
                *sample_count = 0;

        while (1) {
                /*
                 * Reads the mmap data buffer by moving
                 * the data_tail to know the last read data.
                 * data_head points to head in data buffer.
                 * refer "struct perf_event_mmap_page" in
                 * "include/uapi/linux/perf_event.h".
                 */
                if (data_head - data_tail < sizeof(header))
                        return NULL;

                data_tail += sizeof(header);
                if (header->type == PERF_RECORD_SAMPLE) {
                        *size = (header->size - sizeof(header));
                        if (!sample_count)
                                return sample_buff + page_size + data_tail;
                        data_tail += *size;
                        *sample_count += 1;
                } else {
                        *size = (header->size - sizeof(header));
                        if ((metadata_page->data_tail + *size) > metadata_page->data_head)
                                data_tail = metadata_page->data_head;
                        else
                                data_tail += *size;
                }
                header = (struct perf_event_header *)((void *)header + header->size);
        }
        return NULL;
}

int collect_samples(void *sample_buff)
{
        u64 sample_count;
        size_t size = 0;

        __event_read_samples(sample_buff, &size, &sample_count);
        return sample_count;
}

static void *perf_read_first_sample(void *sample_buff, size_t *size)
{
        return __event_read_samples(sample_buff, size, NULL);
}

u64 *get_intr_regs(struct event *event, void *sample_buff)
{
        u64 type = event->attr.sample_type;
        u64 *intr_regs;
        size_t size = 0;

        if ((type ^ (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_BRANCH_STACK)) &&
                        (type  ^ PERF_SAMPLE_REGS_INTR))
                return NULL;

        intr_regs = (u64 *)perf_read_first_sample(sample_buff, &size);
        if (!intr_regs)
                return NULL;

        if (type & PERF_SAMPLE_BRANCH_STACK) {
                /*
                 * PERF_RECORD_SAMPLE and PERF_SAMPLE_BRANCH_STACK:
                 * struct {
                 *     struct perf_event_header hdr;
                 *     u64 number_of_branches;
                 *     struct perf_branch_entry[number_of_branches];
                 *     u64 data[];
                 * };
                 * struct perf_branch_entry {
                 *     u64      from;
                 *     u64      to;
                 *     u64      misc;
                 * };
                 */
                intr_regs += ((*intr_regs) * 3) + 1;
        }

        /*
         * First entry in the sample buffer used to specify
         * PERF_SAMPLE_REGS_ABI_64, skip perf regs abi to access
         * interrupt registers.
         */
        ++intr_regs;

        return intr_regs;
}

static const int __perf_reg_mask(const char *register_name)
{
        if (!strcmp(register_name, "R0"))
                return 0;
        else if (!strcmp(register_name, "R1"))
                return 1;
        else if (!strcmp(register_name, "R2"))
                return 2;
        else if (!strcmp(register_name, "R3"))
                return 3;
        else if (!strcmp(register_name, "R4"))
                return 4;
        else if (!strcmp(register_name, "R5"))
                return 5;
        else if (!strcmp(register_name, "R6"))
                return 6;
        else if (!strcmp(register_name, "R7"))
                return 7;
        else if (!strcmp(register_name, "R8"))
                return 8;
        else if (!strcmp(register_name, "R9"))
                return 9;
        else if (!strcmp(register_name, "R10"))
                return 10;
        else if (!strcmp(register_name, "R11"))
                return 11;
        else if (!strcmp(register_name, "R12"))
                return 12;
        else if (!strcmp(register_name, "R13"))
                return 13;
        else if (!strcmp(register_name, "R14"))
                return 14;
        else if (!strcmp(register_name, "R15"))
                return 15;
        else if (!strcmp(register_name, "R16"))
                return 16;
        else if (!strcmp(register_name, "R17"))
                return 17;
        else if (!strcmp(register_name, "R18"))
                return 18;
        else if (!strcmp(register_name, "R19"))
                return 19;
        else if (!strcmp(register_name, "R20"))
                return 20;
        else if (!strcmp(register_name, "R21"))
                return 21;
        else if (!strcmp(register_name, "R22"))
                return 22;
        else if (!strcmp(register_name, "R23"))
                return 23;
        else if (!strcmp(register_name, "R24"))
                return 24;
        else if (!strcmp(register_name, "R25"))
                return 25;
        else if (!strcmp(register_name, "R26"))
                return 26;
        else if (!strcmp(register_name, "R27"))
                return 27;
        else if (!strcmp(register_name, "R28"))
                return 28;
        else if (!strcmp(register_name, "R29"))
                return 29;
        else if (!strcmp(register_name, "R30"))
                return 30;
        else if (!strcmp(register_name, "R31"))
                return 31;
        else if (!strcmp(register_name, "NIP"))
                return 32;
        else if (!strcmp(register_name, "MSR"))
                return 33;
        else if (!strcmp(register_name, "ORIG_R3"))
                return 34;
        else if (!strcmp(register_name, "CTR"))
                return 35;
        else if (!strcmp(register_name, "LINK"))
                return 36;
        else if (!strcmp(register_name, "XER"))
                return 37;
        else if (!strcmp(register_name, "CCR"))
                return 38;
        else if (!strcmp(register_name, "SOFTE"))
                return 39;
        else if (!strcmp(register_name, "TRAP"))
                return 40;
        else if (!strcmp(register_name, "DAR"))
                return 41;
        else if (!strcmp(register_name, "DSISR"))
                return 42;
        else if (!strcmp(register_name, "SIER"))
                return 43;
        else if (!strcmp(register_name, "MMCRA"))
                return 44;
        else if (!strcmp(register_name, "MMCR0"))
                return 45;
        else if (!strcmp(register_name, "MMCR1"))
                return 46;
        else if (!strcmp(register_name, "MMCR2"))
                return 47;
        else if (!strcmp(register_name, "MMCR3"))
                return 48;
        else if (!strcmp(register_name, "SIER2"))
                return 49;
        else if (!strcmp(register_name, "SIER3"))
                return 50;
        else if (!strcmp(register_name, "PMC1"))
                return 51;
        else if (!strcmp(register_name, "PMC2"))
                return 52;
        else if (!strcmp(register_name, "PMC3"))
                return 53;
        else if (!strcmp(register_name, "PMC4"))
                return 54;
        else if (!strcmp(register_name, "PMC5"))
                return 55;
        else if (!strcmp(register_name, "PMC6"))
                return 56;
        else if (!strcmp(register_name, "SDAR"))
                return 57;
        else if (!strcmp(register_name, "SIAR"))
                return 58;
        else
                return -1;
}

u64 get_reg_value(u64 *intr_regs, char *register_name)
{
        int register_bit_position;

        register_bit_position = __perf_reg_mask(register_name);

        if (register_bit_position < 0 || (!((platform_extended_mask >>
                        (register_bit_position - 1)) & 1)))
                return -1;

        return *(intr_regs + register_bit_position);
}

int get_thresh_cmp_val(struct event event)
{
        int exp = 0;
        u64 result = 0;
        u64 value;

        if (!have_hwcap2(PPC_FEATURE2_ARCH_3_1))
                return EV_CODE_EXTRACT(event.attr.config, thd_cmp);

        value = EV_CODE_EXTRACT(event.attr.config1, thd_cmp);

        if (!value)
                return value;

        /*
         * Incase of P10, thresh_cmp value is not part of raw event code
         * and provided via attr.config1 parameter. To program threshold in MMCRA,
         * take a 18 bit number N and shift right 2 places and increment
         * the exponent E by 1 until the upper 10 bits of N are zero.
         * Write E to the threshold exponent and write the lower 8 bits of N
         * to the threshold mantissa.
         * The max threshold that can be written is 261120.
         */
        if (value > 261120)
                value = 261120;
        while ((64 - __builtin_clzl(value)) > 8) {
                exp++;
                value >>= 2;
        }

        /*
         * Note that it is invalid to write a mantissa with the
         * upper 2 bits of mantissa being zero, unless the
         * exponent is also zero.
         */
        if (!(value & 0xC0) && exp)
                result = -1;
        else
                result = (exp << 8) | value;
        return result;
}

/*
 * Utility function to check for generic compat PMU
 * by comparing base_platform value from auxv and real
 * PVR value.
 * auxv_base_platform() func gives information of "base platform"
 * corresponding to PVR value. Incase, if the distro doesn't
 * support platform PVR (missing cputable support), base platform
 * in auxv will have a default value other than the real PVR's.
 * In this case, ISAv3 PMU (generic compat PMU) will be registered
 * in the system. auxv_generic_compat_pmu() makes use of the base
 * platform value from auxv to do this check.
 */
static bool auxv_generic_compat_pmu(void)
{
        int base_pvr = 0;

        if (!strcmp(auxv_base_platform(), "power9"))
                base_pvr = POWER9;
        else if (!strcmp(auxv_base_platform(), "power10"))
                base_pvr = POWER10;
        else if (!strcmp(auxv_base_platform(), "power11"))
                base_pvr = POWER11;

        return (!base_pvr);
}

/*
 * Check for generic compat PMU.
 * First check for presence of pmu_name from
 * "/sys/bus/event_source/devices/cpu/caps".
 * If doesn't exist, fallback to using value
 * auxv.
 */
bool check_for_generic_compat_pmu(void)
{
        char pmu_name[256];

        memset(pmu_name, 0, sizeof(pmu_name));
        if (read_sysfs_file("bus/event_source/devices/cpu/caps/pmu_name",
                pmu_name, sizeof(pmu_name)) < 0)
                return auxv_generic_compat_pmu();

        if (!strcmp(pmu_name, "ISAv3"))
                return true;
        else
                return false;
}

/*
 * Check if system is booted in compat mode.
 */
bool check_for_compat_mode(void)
{
        char *platform = auxv_platform();
        char *base_platform = auxv_base_platform();

        return strcmp(platform, base_platform);
}