// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include #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 = * 2 Sample period mode * perf_event_attr->freq = 0, ->sample_period = * * Instead of using above interface, IBS event in 'sample period mode' * can also be opened by passing 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; }