root/tools/perf/arch/x86/tests/amd-ibs-period.c
// SPDX-License-Identifier: GPL-2.0
#include <sched.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <string.h>

#include "arch-tests.h"
#include "linux/perf_event.h"
#include "linux/zalloc.h"
#include "tests/tests.h"
#include "../perf-sys.h"
#include "pmu.h"
#include "pmus.h"
#include "debug.h"
#include "util.h"
#include "strbuf.h"
#include "../util/env.h"

static int page_size;

#define PERF_MMAP_DATA_PAGES    32L
#define PERF_MMAP_DATA_SIZE     (PERF_MMAP_DATA_PAGES * page_size)
#define PERF_MMAP_DATA_MASK     (PERF_MMAP_DATA_SIZE - 1)
#define PERF_MMAP_TOTAL_PAGES   (PERF_MMAP_DATA_PAGES + 1)
#define PERF_MMAP_TOTAL_SIZE    (PERF_MMAP_TOTAL_PAGES * page_size)

#define rmb()                   asm volatile("lfence":::"memory")

enum {
        FD_ERROR,
        FD_SUCCESS,
};

enum {
        IBS_FETCH,
        IBS_OP,
};

struct perf_pmu *fetch_pmu;
struct perf_pmu *op_pmu;
unsigned int perf_event_max_sample_rate;

/* Dummy workload to generate IBS samples. */
static int dummy_workload_1(unsigned long count)
{
        int (*func)(void);
        int ret = 0;
        char *p;
        char insn1[] = {
                0xb8, 0x01, 0x00, 0x00, 0x00, /* mov 1,%eax */
                0xc3, /* ret */
                0xcc, /* int 3 */
        };

        char insn2[] = {
                0xb8, 0x02, 0x00, 0x00, 0x00, /* mov 2,%eax */
                0xc3, /* ret */
                0xcc, /* int 3 */
        };

        p = zalloc(2 * page_size);
        if (!p) {
                printf("malloc() failed. %m");
                return 1;
        }

        func = (void *)((unsigned long)(p + page_size - 1) & ~(page_size - 1));

        ret = mprotect(func, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
        if (ret) {
                printf("mprotect() failed. %m");
                goto out;
        }

        if (count < 100000)
                count = 100000;
        else if (count > 10000000)
                count = 10000000;
        while (count--) {
                memcpy((void *)func, insn1, sizeof(insn1));
                if (func() != 1) {
                        pr_debug("ERROR insn1\n");
                        ret = -1;
                        goto out;
                }
                memcpy((void *)func, insn2, sizeof(insn2));
                if (func() != 2) {
                        pr_debug("ERROR insn2\n");
                        ret = -1;
                        goto out;
                }
        }

out:
        free(p);
        return ret;
}

/* Another dummy workload to generate IBS samples. */
static void dummy_workload_2(char *perf)
{
        char bench[] = " bench sched messaging -g 10 -l 5000 > /dev/null 2>&1";
        char taskset[] = "taskset -c 0 ";
        int ret __maybe_unused;
        struct strbuf sb;
        char *cmd;

        strbuf_init(&sb, 0);
        strbuf_add(&sb, taskset, strlen(taskset));
        strbuf_add(&sb, perf, strlen(perf));
        strbuf_add(&sb, bench, strlen(bench));
        cmd = strbuf_detach(&sb, NULL);
        ret = system(cmd);
        free(cmd);
}

static int sched_affine(int cpu)
{
        cpu_set_t set;

        CPU_ZERO(&set);
        CPU_SET(cpu, &set);
        if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) {
                pr_debug("sched_setaffinity() failed. [%m]");
                return -1;
        }
        return 0;
}

static void
copy_sample_data(void *src, unsigned long offset, void *dest, size_t size)
{
        size_t chunk1_size, chunk2_size;

        if ((offset + size) < (size_t)PERF_MMAP_DATA_SIZE) {
                memcpy(dest, src + offset, size);
        } else {
                chunk1_size = PERF_MMAP_DATA_SIZE - offset;
                chunk2_size = size - chunk1_size;

                memcpy(dest, src + offset, chunk1_size);
                memcpy(dest + chunk1_size, src, chunk2_size);
        }
}

static int rb_read(struct perf_event_mmap_page *rb, void *dest, size_t size)
{
        void *base;
        unsigned long data_tail, data_head;

        /* Casting to (void *) is needed. */
        base = (void *)rb + page_size;

        data_head = rb->data_head;
        rmb();
        data_tail = rb->data_tail;

        if ((data_head - data_tail) < size)
                return -1;

        data_tail &= PERF_MMAP_DATA_MASK;
        copy_sample_data(base, data_tail, dest, size);
        rb->data_tail += size;
        return 0;
}

static void rb_skip(struct perf_event_mmap_page *rb, size_t size)
{
        size_t data_head = rb->data_head;

        rmb();

        if ((rb->data_tail + size) > data_head)
                rb->data_tail = data_head;
        else
                rb->data_tail += size;
}

/* Sample period value taken from perf sample must match with expected value. */
static int period_equal(unsigned long exp_period, unsigned long act_period)
{
        return exp_period == act_period ? 0 : -1;
}

/*
 * Sample period value taken from perf sample must be >= minimum sample period
 * supported by IBS HW.
 */
static int period_higher(unsigned long min_period, unsigned long act_period)
{
        return min_period <= act_period ? 0 : -1;
}

static int rb_drain_samples(struct perf_event_mmap_page *rb,
                            unsigned long exp_period,
                            int *nr_samples,
                            int (*callback)(unsigned long, unsigned long))
{
        struct perf_event_header hdr;
        unsigned long period;
        int ret = 0;

        /*
         * PERF_RECORD_SAMPLE:
         * struct {
         *      struct perf_event_header hdr;
         *      { u64                    period;     } && PERF_SAMPLE_PERIOD
         * };
         */
        while (1) {
                if (rb_read(rb, &hdr, sizeof(hdr)))
                        return ret;

                if (hdr.type == PERF_RECORD_SAMPLE) {
                        (*nr_samples)++;
                        period = 0;
                        if (rb_read(rb, &period, sizeof(period)))
                                pr_debug("rb_read(period) error. [%m]");
                        ret |= callback(exp_period, period);
                } else {
                        rb_skip(rb, hdr.size - sizeof(hdr));
                }
        }
        return ret;
}

static long perf_event_open(struct perf_event_attr *attr, pid_t pid,
                            int cpu, int group_fd, unsigned long flags)
{
        return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
}

static void fetch_prepare_attr(struct perf_event_attr *attr,
                               unsigned long long config, int freq,
                               unsigned long sample_period)
{
        memset(attr, 0, sizeof(struct perf_event_attr));

        attr->type = fetch_pmu->type;
        attr->size = sizeof(struct perf_event_attr);
        attr->config = config;
        attr->disabled = 1;
        attr->sample_type = PERF_SAMPLE_PERIOD;
        attr->freq = freq;
        attr->sample_period = sample_period; /* = ->sample_freq */
}

static void op_prepare_attr(struct perf_event_attr *attr,
                            unsigned long config, int freq,
                            unsigned long sample_period)
{
        memset(attr, 0, sizeof(struct perf_event_attr));

        attr->type = op_pmu->type;
        attr->size = sizeof(struct perf_event_attr);
        attr->config = config;
        attr->disabled = 1;
        attr->sample_type = PERF_SAMPLE_PERIOD;
        attr->freq = freq;
        attr->sample_period = sample_period; /* = ->sample_freq */
}

struct ibs_configs {
        /* Input */
        unsigned long config;

        /* Expected output */
        unsigned long period;
        int fd;
};

/*
 * Somehow first Fetch event with sample period = 0x10 causes 0
 * samples. So start with large period and decrease it gradually.
 */
struct ibs_configs fetch_configs[] = {
        { .config =  0xffff, .period = 0xffff0, .fd = FD_SUCCESS },
        { .config =  0x1000, .period = 0x10000, .fd = FD_SUCCESS },
        { .config =    0xff, .period =   0xff0, .fd = FD_SUCCESS },
        { .config =     0x1, .period =    0x10, .fd = FD_SUCCESS },
        { .config =     0x0, .period =      -1, .fd = FD_ERROR   },
        { .config = 0x10000, .period =      -1, .fd = FD_ERROR   },
};

struct ibs_configs op_configs[] = {
        { .config =        0x0, .period =        -1, .fd = FD_ERROR   },
        { .config =        0x1, .period =        -1, .fd = FD_ERROR   },
        { .config =        0x8, .period =        -1, .fd = FD_ERROR   },
        { .config =        0x9, .period =      0x90, .fd = FD_SUCCESS },
        { .config =        0xf, .period =      0xf0, .fd = FD_SUCCESS },
        { .config =     0x1000, .period =   0x10000, .fd = FD_SUCCESS },
        { .config =     0xffff, .period =   0xffff0, .fd = FD_SUCCESS },
        { .config =    0x10000, .period =        -1, .fd = FD_ERROR   },
        { .config =   0x100000, .period =  0x100000, .fd = FD_SUCCESS },
        { .config =   0xf00000, .period =  0xf00000, .fd = FD_SUCCESS },
        { .config =   0xf0ffff, .period =  0xfffff0, .fd = FD_SUCCESS },
        { .config =  0x1f0ffff, .period = 0x1fffff0, .fd = FD_SUCCESS },
        { .config =  0x7f0ffff, .period = 0x7fffff0, .fd = FD_SUCCESS },
        { .config =  0x8f0ffff, .period =        -1, .fd = FD_ERROR   },
        { .config = 0x17f0ffff, .period =        -1, .fd = FD_ERROR   },
};

static int __ibs_config_test(int ibs_type, struct ibs_configs *config, int *nr_samples)
{
        struct perf_event_attr attr;
        int fd, i;
        void *rb;
        int ret = 0;

        if (ibs_type == IBS_FETCH)
                fetch_prepare_attr(&attr, config->config, 0, 0);
        else
                op_prepare_attr(&attr, config->config, 0, 0);

        /* CPU0, All processes */
        fd = perf_event_open(&attr, -1, 0, -1, 0);
        if (config->fd == FD_ERROR) {
                if (fd != -1) {
                        close(fd);
                        return -1;
                }
                return 0;
        }
        if (fd <= -1)
                return -1;

        rb = mmap(NULL, PERF_MMAP_TOTAL_SIZE, PROT_READ | PROT_WRITE,
                  MAP_SHARED, fd, 0);
        if (rb == MAP_FAILED) {
                pr_debug("mmap() failed. [%m]\n");
                return -1;
        }

        ioctl(fd, PERF_EVENT_IOC_RESET, 0);
        ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

        i = 5;
        while (i--) {
                dummy_workload_1(1000000);

                ret = rb_drain_samples(rb, config->period, nr_samples,
                                       period_equal);
                if (ret)
                        break;
        }

        ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
        munmap(rb, PERF_MMAP_TOTAL_SIZE);
        close(fd);
        return ret;
}

static int ibs_config_test(void)
{
        int nr_samples = 0;
        unsigned long i;
        int ret = 0;
        int r;

        pr_debug("\nIBS config tests:\n");
        pr_debug("-----------------\n");

        pr_debug("Fetch PMU tests:\n");
        for (i = 0; i < ARRAY_SIZE(fetch_configs); i++) {
                nr_samples = 0;
                r = __ibs_config_test(IBS_FETCH, &(fetch_configs[i]), &nr_samples);

                if (fetch_configs[i].fd == FD_ERROR) {
                        pr_debug("0x%-16lx: %-4s\n", fetch_configs[i].config,
                                 !r ? "Ok" : "Fail");
                } else {
                        /*
                         * Although nr_samples == 0 is reported as Fail here,
                         * the failure status is not cascaded up because, we
                         * can not decide whether test really failed or not
                         * without actual samples.
                         */
                        pr_debug("0x%-16lx: %-4s (nr samples: %d)\n", fetch_configs[i].config,
                                 (!r && nr_samples != 0) ? "Ok" : "Fail", nr_samples);
                }

                ret |= r;
        }

        pr_debug("Op PMU tests:\n");
        for (i = 0; i < ARRAY_SIZE(op_configs); i++) {
                nr_samples = 0;
                r = __ibs_config_test(IBS_OP, &(op_configs[i]), &nr_samples);

                if (op_configs[i].fd == FD_ERROR) {
                        pr_debug("0x%-16lx: %-4s\n", op_configs[i].config,
                                 !r ? "Ok" : "Fail");
                } else {
                        /*
                         * Although nr_samples == 0 is reported as Fail here,
                         * the failure status is not cascaded up because, we
                         * can not decide whether test really failed or not
                         * without actual samples.
                         */
                        pr_debug("0x%-16lx: %-4s (nr samples: %d)\n", op_configs[i].config,
                                 (!r && nr_samples != 0) ? "Ok" : "Fail", nr_samples);
                }

                ret |= r;
        }

        return ret;
}

struct ibs_period {
        /* Input */
        int freq;
        unsigned long sample_freq;

        /* Output */
        int ret;
        unsigned long period;
};

struct ibs_period fetch_period[] = {
        { .freq = 0, .sample_freq =         0, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 0, .sample_freq =         1, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 0, .sample_freq =       0xf, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 0, .sample_freq =      0x10, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 0, .sample_freq =      0x11, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 0, .sample_freq =      0x8f, .ret = FD_SUCCESS, .period =      0x80 },
        { .freq = 0, .sample_freq =      0x90, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 0, .sample_freq =      0x91, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 0, .sample_freq =     0x4d2, .ret = FD_SUCCESS, .period =     0x4d0 },
        { .freq = 0, .sample_freq =    0x1007, .ret = FD_SUCCESS, .period =    0x1000 },
        { .freq = 0, .sample_freq =    0xfff0, .ret = FD_SUCCESS, .period =    0xfff0 },
        { .freq = 0, .sample_freq =    0xffff, .ret = FD_SUCCESS, .period =    0xfff0 },
        { .freq = 0, .sample_freq =   0x10010, .ret = FD_SUCCESS, .period =   0x10010 },
        { .freq = 0, .sample_freq =  0x7fffff, .ret = FD_SUCCESS, .period =  0x7ffff0 },
        { .freq = 0, .sample_freq = 0xfffffff, .ret = FD_SUCCESS, .period = 0xffffff0 },
        { .freq = 1, .sample_freq =         0, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 1, .sample_freq =         1, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =       0xf, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =      0x10, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =      0x11, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =      0x8f, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =      0x90, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =      0x91, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =     0x4d2, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =    0x1007, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =    0xfff0, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =    0xffff, .ret = FD_SUCCESS, .period =      0x10 },
        { .freq = 1, .sample_freq =   0x10010, .ret = FD_SUCCESS, .period =      0x10 },
        /* ret=FD_ERROR because freq > default perf_event_max_sample_rate (100000) */
        { .freq = 1, .sample_freq =  0x7fffff, .ret = FD_ERROR,   .period =        -1 },
};

struct ibs_period op_period[] = {
        { .freq = 0, .sample_freq =         0, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 0, .sample_freq =         1, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 0, .sample_freq =       0xf, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 0, .sample_freq =      0x10, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 0, .sample_freq =      0x11, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 0, .sample_freq =      0x8f, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 0, .sample_freq =      0x90, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 0, .sample_freq =      0x91, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 0, .sample_freq =     0x4d2, .ret = FD_SUCCESS, .period =     0x4d0 },
        { .freq = 0, .sample_freq =    0x1007, .ret = FD_SUCCESS, .period =    0x1000 },
        { .freq = 0, .sample_freq =    0xfff0, .ret = FD_SUCCESS, .period =    0xfff0 },
        { .freq = 0, .sample_freq =    0xffff, .ret = FD_SUCCESS, .period =    0xfff0 },
        { .freq = 0, .sample_freq =   0x10010, .ret = FD_SUCCESS, .period =   0x10010 },
        { .freq = 0, .sample_freq =  0x7fffff, .ret = FD_SUCCESS, .period =  0x7ffff0 },
        { .freq = 0, .sample_freq = 0xfffffff, .ret = FD_SUCCESS, .period = 0xffffff0 },
        { .freq = 1, .sample_freq =         0, .ret = FD_ERROR,   .period =        -1 },
        { .freq = 1, .sample_freq =         1, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =       0xf, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =      0x10, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =      0x11, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =      0x8f, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =      0x90, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =      0x91, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =     0x4d2, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =    0x1007, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =    0xfff0, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =    0xffff, .ret = FD_SUCCESS, .period =      0x90 },
        { .freq = 1, .sample_freq =   0x10010, .ret = FD_SUCCESS, .period =      0x90 },
        /* ret=FD_ERROR because freq > default perf_event_max_sample_rate (100000) */
        { .freq = 1, .sample_freq =  0x7fffff, .ret = FD_ERROR,   .period =        -1 },
};

static int __ibs_period_constraint_test(int ibs_type, struct ibs_period *period,
                                        int *nr_samples)
{
        struct perf_event_attr attr;
        int ret = 0;
        void *rb;
        int fd;

        if (period->freq && period->sample_freq > perf_event_max_sample_rate)
                period->ret = FD_ERROR;

        if (ibs_type == IBS_FETCH)
                fetch_prepare_attr(&attr, 0, period->freq, period->sample_freq);
        else
                op_prepare_attr(&attr, 0, period->freq, period->sample_freq);

        /* CPU0, All processes */
        fd = perf_event_open(&attr, -1, 0, -1, 0);
        if (period->ret == FD_ERROR) {
                if (fd != -1) {
                        close(fd);
                        return -1;
                }
                return 0;
        }
        if (fd <= -1)
                return -1;

        rb = mmap(NULL, PERF_MMAP_TOTAL_SIZE, PROT_READ | PROT_WRITE,
                  MAP_SHARED, fd, 0);
        if (rb == MAP_FAILED) {
                pr_debug("mmap() failed. [%m]\n");
                close(fd);
                return -1;
        }

        ioctl(fd, PERF_EVENT_IOC_RESET, 0);
        ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

        if (period->freq) {
                dummy_workload_1(100000);
                ret = rb_drain_samples(rb, period->period, nr_samples,
                                       period_higher);
        } else {
                dummy_workload_1(period->sample_freq * 10);
                ret = rb_drain_samples(rb, period->period, nr_samples,
                                       period_equal);
        }

        ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
        munmap(rb, PERF_MMAP_TOTAL_SIZE);
        close(fd);
        return ret;
}

static int ibs_period_constraint_test(void)
{
        unsigned long i;
        int nr_samples;
        int ret = 0;
        int r;

        pr_debug("\nIBS sample period constraint tests:\n");
        pr_debug("-----------------------------------\n");

        pr_debug("Fetch PMU test:\n");
        for (i = 0; i < ARRAY_SIZE(fetch_period); i++) {
                nr_samples = 0;
                r = __ibs_period_constraint_test(IBS_FETCH, &fetch_period[i],
                                                 &nr_samples);

                if (fetch_period[i].ret == FD_ERROR) {
                        pr_debug("freq %d, sample_freq %9ld: %-4s\n",
                                 fetch_period[i].freq, fetch_period[i].sample_freq,
                                 !r ? "Ok" : "Fail");
                } else {
                        /*
                         * Although nr_samples == 0 is reported as Fail here,
                         * the failure status is not cascaded up because, we
                         * can not decide whether test really failed or not
                         * without actual samples.
                         */
                        pr_debug("freq %d, sample_freq %9ld: %-4s (nr samples: %d)\n",
                                 fetch_period[i].freq, fetch_period[i].sample_freq,
                                 (!r && nr_samples != 0) ? "Ok" : "Fail", nr_samples);
                }
                ret |= r;
        }

        pr_debug("Op PMU test:\n");
        for (i = 0; i < ARRAY_SIZE(op_period); i++) {
                nr_samples = 0;
                r = __ibs_period_constraint_test(IBS_OP, &op_period[i],
                                                 &nr_samples);

                if (op_period[i].ret == FD_ERROR) {
                        pr_debug("freq %d, sample_freq %9ld: %-4s\n",
                                 op_period[i].freq, op_period[i].sample_freq,
                                 !r ? "Ok" : "Fail");
                } else {
                        /*
                         * Although nr_samples == 0 is reported as Fail here,
                         * the failure status is not cascaded up because, we
                         * can not decide whether test really failed or not
                         * without actual samples.
                         */
                        pr_debug("freq %d, sample_freq %9ld: %-4s (nr samples: %d)\n",
                                 op_period[i].freq, op_period[i].sample_freq,
                                 (!r && nr_samples != 0) ? "Ok" : "Fail", nr_samples);
                }
                ret |= r;
        }

        return ret;
}

struct ibs_ioctl {
        /* Input */
        int freq;
        unsigned long period;

        /* Expected output */
        int ret;
};

struct ibs_ioctl fetch_ioctl[] = {
        { .freq = 0, .period =     0x0, .ret = FD_ERROR   },
        { .freq = 0, .period =     0x1, .ret = FD_ERROR   },
        { .freq = 0, .period =     0xf, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x10, .ret = FD_SUCCESS },
        { .freq = 0, .period =    0x11, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x1f, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x20, .ret = FD_SUCCESS },
        { .freq = 0, .period =    0x80, .ret = FD_SUCCESS },
        { .freq = 0, .period =    0x8f, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x90, .ret = FD_SUCCESS },
        { .freq = 0, .period =    0x91, .ret = FD_ERROR   },
        { .freq = 0, .period =   0x100, .ret = FD_SUCCESS },
        { .freq = 0, .period =  0xfff0, .ret = FD_SUCCESS },
        { .freq = 0, .period =  0xffff, .ret = FD_ERROR   },
        { .freq = 0, .period = 0x10000, .ret = FD_SUCCESS },
        { .freq = 0, .period = 0x1fff0, .ret = FD_SUCCESS },
        { .freq = 0, .period = 0x1fff5, .ret = FD_ERROR   },
        { .freq = 1, .period =     0x0, .ret = FD_ERROR   },
        { .freq = 1, .period =     0x1, .ret = FD_SUCCESS },
        { .freq = 1, .period =     0xf, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x10, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x11, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x1f, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x20, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x80, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x8f, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x90, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x91, .ret = FD_SUCCESS },
        { .freq = 1, .period =   0x100, .ret = FD_SUCCESS },
};

struct ibs_ioctl op_ioctl[] = {
        { .freq = 0, .period =     0x0, .ret = FD_ERROR   },
        { .freq = 0, .period =     0x1, .ret = FD_ERROR   },
        { .freq = 0, .period =     0xf, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x10, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x11, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x1f, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x20, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x80, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x8f, .ret = FD_ERROR   },
        { .freq = 0, .period =    0x90, .ret = FD_SUCCESS },
        { .freq = 0, .period =    0x91, .ret = FD_ERROR   },
        { .freq = 0, .period =   0x100, .ret = FD_SUCCESS },
        { .freq = 0, .period =  0xfff0, .ret = FD_SUCCESS },
        { .freq = 0, .period =  0xffff, .ret = FD_ERROR   },
        { .freq = 0, .period = 0x10000, .ret = FD_SUCCESS },
        { .freq = 0, .period = 0x1fff0, .ret = FD_SUCCESS },
        { .freq = 0, .period = 0x1fff5, .ret = FD_ERROR   },
        { .freq = 1, .period =     0x0, .ret = FD_ERROR   },
        { .freq = 1, .period =     0x1, .ret = FD_SUCCESS },
        { .freq = 1, .period =     0xf, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x10, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x11, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x1f, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x20, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x80, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x8f, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x90, .ret = FD_SUCCESS },
        { .freq = 1, .period =    0x91, .ret = FD_SUCCESS },
        { .freq = 1, .period =   0x100, .ret = FD_SUCCESS },
};

static int __ibs_ioctl_test(int ibs_type, struct ibs_ioctl *ibs_ioctl)
{
        struct perf_event_attr attr;
        int ret = 0;
        int fd;
        int r;

        if (ibs_type == IBS_FETCH)
                fetch_prepare_attr(&attr, 0, ibs_ioctl->freq, 1000);
        else
                op_prepare_attr(&attr, 0, ibs_ioctl->freq, 1000);

        /* CPU0, All processes */
        fd = perf_event_open(&attr, -1, 0, -1, 0);
        if (fd <= -1) {
                pr_debug("event_open() Failed\n");
                return -1;
        }

        r = ioctl(fd, PERF_EVENT_IOC_PERIOD, &ibs_ioctl->period);
        if ((ibs_ioctl->ret == FD_SUCCESS && r <= -1) ||
            (ibs_ioctl->ret == FD_ERROR && r >= 0)) {
                ret = -1;
        }

        close(fd);
        return ret;
}

static int ibs_ioctl_test(void)
{
        unsigned long i;
        int ret = 0;
        int r;

        pr_debug("\nIBS ioctl() tests:\n");
        pr_debug("------------------\n");

        pr_debug("Fetch PMU tests\n");
        for (i = 0; i < ARRAY_SIZE(fetch_ioctl); i++) {
                r = __ibs_ioctl_test(IBS_FETCH, &fetch_ioctl[i]);

                pr_debug("ioctl(%s = 0x%-7lx): %s\n",
                         fetch_ioctl[i].freq ? "freq  " : "period",
                         fetch_ioctl[i].period, r ? "Fail" : "Ok");
                ret |= r;
        }

        pr_debug("Op PMU tests\n");
        for (i = 0; i < ARRAY_SIZE(op_ioctl); i++) {
                r = __ibs_ioctl_test(IBS_OP, &op_ioctl[i]);

                pr_debug("ioctl(%s = 0x%-7lx): %s\n",
                         op_ioctl[i].freq ? "freq  " : "period",
                         op_ioctl[i].period, r ? "Fail" : "Ok");
                ret |= r;
        }

        return ret;
}

static int ibs_freq_neg_test(void)
{
        struct perf_event_attr attr;
        int fd;

        pr_debug("\nIBS freq (negative) tests:\n");
        pr_debug("--------------------------\n");

        /*
         * Assuming perf_event_max_sample_rate <= 100000,
         * config: 0x300D40 ==> MaxCnt: 200000
         */
        op_prepare_attr(&attr, 0x300D40, 1, 0);

        /* CPU0, All processes */
        fd = perf_event_open(&attr, -1, 0, -1, 0);
        if (fd != -1) {
                pr_debug("freq 1, sample_freq 200000: Fail\n");
                close(fd);
                return -1;
        }

        pr_debug("freq 1, sample_freq 200000: Ok\n");

        return 0;
}

struct ibs_l3missonly {
        /* Input */
        int freq;
        unsigned long sample_freq;

        /* Expected output */
        int ret;
        unsigned long min_period;
};

struct ibs_l3missonly fetch_l3missonly = {
        .freq = 1,
        .sample_freq = 10000,
        .ret = FD_SUCCESS,
        .min_period = 0x10,
};

struct ibs_l3missonly op_l3missonly = {
        .freq = 1,
        .sample_freq = 10000,
        .ret = FD_SUCCESS,
        .min_period = 0x90,
};

static int __ibs_l3missonly_test(char *perf, int ibs_type, int *nr_samples,
                                 struct ibs_l3missonly *l3missonly)
{
        struct perf_event_attr attr;
        int ret = 0;
        void *rb;
        int fd;

        if (l3missonly->sample_freq > perf_event_max_sample_rate)
                l3missonly->ret = FD_ERROR;

        if (ibs_type == IBS_FETCH) {
                fetch_prepare_attr(&attr, 0x800000000000000UL, l3missonly->freq,
                                   l3missonly->sample_freq);
        } else {
                op_prepare_attr(&attr, 0x10000, l3missonly->freq,
                                l3missonly->sample_freq);
        }

        /* CPU0, All processes */
        fd = perf_event_open(&attr, -1, 0, -1, 0);
        if (l3missonly->ret == FD_ERROR) {
                if (fd != -1) {
                        close(fd);
                        return -1;
                }
                return 0;
        }
        if (fd == -1) {
                pr_debug("perf_event_open() failed. [%m]\n");
                return -1;
        }

        rb = mmap(NULL, PERF_MMAP_TOTAL_SIZE, PROT_READ | PROT_WRITE,
                  MAP_SHARED, fd, 0);
        if (rb == MAP_FAILED) {
                pr_debug("mmap() failed. [%m]\n");
                close(fd);
                return -1;
        }

        ioctl(fd, PERF_EVENT_IOC_RESET, 0);
        ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

        dummy_workload_2(perf);

        ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);

        ret = rb_drain_samples(rb, l3missonly->min_period, nr_samples, period_higher);

        munmap(rb, PERF_MMAP_TOTAL_SIZE);
        close(fd);
        return ret;
}

static int ibs_l3missonly_test(char *perf)
{
        int nr_samples = 0;
        int ret = 0;
        int r = 0;

        pr_debug("\nIBS L3MissOnly test: (takes a while)\n");
        pr_debug("--------------------\n");

        if (perf_pmu__has_format(fetch_pmu, "l3missonly")) {
                nr_samples = 0;
                r = __ibs_l3missonly_test(perf, IBS_FETCH, &nr_samples, &fetch_l3missonly);
                if (fetch_l3missonly.ret == FD_ERROR) {
                        pr_debug("Fetch L3MissOnly: %-4s\n", !r ? "Ok" : "Fail");
                } else {
                        /*
                         * Although nr_samples == 0 is reported as Fail here,
                         * the failure status is not cascaded up because, we
                         * can not decide whether test really failed or not
                         * without actual samples.
                         */
                        pr_debug("Fetch L3MissOnly: %-4s (nr_samples: %d)\n",
                                 (!r && nr_samples != 0) ? "Ok" : "Fail", nr_samples);
                }
                ret |= r;
        }

        if (perf_pmu__has_format(op_pmu, "l3missonly")) {
                nr_samples = 0;
                r = __ibs_l3missonly_test(perf, IBS_OP, &nr_samples, &op_l3missonly);
                if (op_l3missonly.ret == FD_ERROR) {
                        pr_debug("Op L3MissOnly:    %-4s\n", !r ? "Ok" : "Fail");
                } else {
                        /*
                         * Although nr_samples == 0 is reported as Fail here,
                         * the failure status is not cascaded up because, we
                         * can not decide whether test really failed or not
                         * without actual samples.
                         */
                        pr_debug("Op L3MissOnly:    %-4s (nr_samples: %d)\n",
                                 (!r && nr_samples != 0) ? "Ok" : "Fail", nr_samples);
                }
                ret |= r;
        }

        return ret;
}

static unsigned int get_perf_event_max_sample_rate(void)
{
        unsigned int max_sample_rate = 100000;
        FILE *fp;
        int ret;

        fp = fopen("/proc/sys/kernel/perf_event_max_sample_rate", "r");
        if (!fp) {
                pr_debug("Can't open perf_event_max_sample_rate. Assuming %d\n",
                         max_sample_rate);
                goto out;
        }

        ret = fscanf(fp, "%d", &max_sample_rate);
        if (ret == EOF) {
                pr_debug("Can't read perf_event_max_sample_rate. Assuming 100000\n");
                max_sample_rate = 100000;
        }
        fclose(fp);

out:
        return max_sample_rate;
}

/*
 * Bunch of IBS sample period fixes that this test exercise went in v6.15.
 * Skip the test on older kernels to distinguish between test failure due
 * to a new bug vs known failure due to older kernel.
 */
static bool kernel_v6_15_or_newer(void)
{
        struct utsname utsname;
        char *endptr = NULL;
        long major, minor;

        if (uname(&utsname) < 0) {
                pr_debug("uname() failed. [%m]");
                return false;
        }

        major = strtol(utsname.release, &endptr, 10);
        endptr++;
        minor = strtol(endptr, NULL, 10);

        return major >= 6 && minor >= 15;
}

int test__amd_ibs_period(struct test_suite *test __maybe_unused,
                         int subtest __maybe_unused)
{
        char perf[PATH_MAX] = {'\0'};
        int ret = TEST_OK;

        page_size = sysconf(_SC_PAGESIZE);

        /*
         * Reading perf_event_max_sample_rate only once _might_ cause some
         * of the test to fail if kernel changes it after reading it here.
         */
        perf_event_max_sample_rate = get_perf_event_max_sample_rate();
        fetch_pmu = perf_pmus__find("ibs_fetch");
        op_pmu = perf_pmus__find("ibs_op");

        if (!x86__is_amd_cpu() || !fetch_pmu || !op_pmu)
                return TEST_SKIP;

        if (!kernel_v6_15_or_newer()) {
                pr_debug("Need v6.15 or newer kernel. Skipping.\n");
                return TEST_SKIP;
        }

        perf_exe(perf, sizeof(perf));

        if (sched_affine(0))
                return TEST_FAIL;

        /*
         * Perf event can be opened in two modes:
         * 1 Freq mode
         *   perf_event_attr->freq = 1, ->sample_freq = <frequency>
         * 2 Sample period mode
         *   perf_event_attr->freq = 0, ->sample_period = <period>
         *
         * Instead of using above interface, IBS event in 'sample period mode'
         * can also be opened by passing <period> value directly in a MaxCnt
         * bitfields of perf_event_attr->config. Test this IBS specific special
         * interface.
         */
        if (ibs_config_test())
                ret = TEST_FAIL;

        /*
         * IBS Fetch and Op PMUs have HW constraints on minimum sample period.
         * Also, sample period value must be in multiple of 0x10. Test that IBS
         * driver honors HW constraints for various possible values in Freq as
         * well as Sample Period mode IBS events.
         */
        if (ibs_period_constraint_test())
                ret = TEST_FAIL;

        /*
         * Test ioctl() with various sample period values for IBS event.
         */
        if (ibs_ioctl_test())
                ret = TEST_FAIL;

        /*
         * Test that opening of freq mode IBS event fails when the freq value
         * is passed through ->config, not explicitly in ->sample_freq. Also
         * use high freq value (beyond perf_event_max_sample_rate) to test IBS
         * driver do not bypass perf_event_max_sample_rate checks.
         */
        if (ibs_freq_neg_test())
                ret = TEST_FAIL;

        /*
         * L3MissOnly is a post-processing filter, i.e. IBS HW checks for L3
         * Miss at the completion of the tagged uOp. The sample is discarded
         * if the tagged uOp did not cause L3Miss. Also, IBS HW internally
         * resets CurCnt to a small pseudo-random value and resumes counting.
         * A new uOp is tagged once CurCnt reaches to MaxCnt. But the process
         * repeats until the tagged uOp causes an L3 Miss.
         *
         * With the freq mode event, the next sample period is calculated by
         * generic kernel on every sample to achieve desired freq of samples.
         *
         * Since the number of times HW internally reset CurCnt and the pseudo-
         * random value of CurCnt for all those occurrences are not known to SW,
         * the sample period adjustment by kernel goes for a toes for freq mode
         * IBS events. Kernel will set very small period for the next sample if
         * the window between current sample and prev sample is too high due to
         * multiple samples being discarded internally by IBS HW.
         *
         * Test that IBS sample period constraints are honored when L3MissOnly
         * is ON.
         */
        if (ibs_l3missonly_test(perf))
                ret = TEST_FAIL;

        return ret;
}