// 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 32 L
#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[] = {
0 xb8, 0 x01, 0 x00, 0 x00, 0 x00, /* mov 1,%eax */
0 xc3, /* ret */
0 xcc, /* int 3 */
};
char insn2[] = {
0 xb8, 0 x02, 0 x00, 0 x00, 0 x00, /* mov 2,%eax */
0 xc3, /* ret */
0 xcc, /* 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 = 0 x10 causes 0
* samples . So start with large period and decrease it gradually .
*/
struct ibs_configs fetch_configs[] = {
{ .config = 0 xffff, .period = 0 xffff0, .fd = FD_SUCCESS },
{ .config = 0 x1000, .period = 0 x10000, .fd = FD_SUCCESS },
{ .config = 0 xff, .period = 0 xff0, .fd = FD_SUCCESS },
{ .config = 0 x1, .period = 0 x10, .fd = FD_SUCCESS },
{ .config = 0 x0, .period = -1 , .fd = FD_ERROR },
{ .config = 0 x10000, .period = -1 , .fd = FD_ERROR },
};
struct ibs_configs op_configs[] = {
{ .config = 0 x0, .period = -1 , .fd = FD_ERROR },
{ .config = 0 x1, .period = -1 , .fd = FD_ERROR },
{ .config = 0 x8, .period = -1 , .fd = FD_ERROR },
{ .config = 0 x9, .period = 0 x90, .fd = FD_SUCCESS },
{ .config = 0 xf, .period = 0 xf0, .fd = FD_SUCCESS },
{ .config = 0 x1000, .period = 0 x10000, .fd = FD_SUCCESS },
{ .config = 0 xffff, .period = 0 xffff0, .fd = FD_SUCCESS },
{ .config = 0 x10000, .period = -1 , .fd = FD_ERROR },
{ .config = 0 x100000, .period = 0 x100000, .fd = FD_SUCCESS },
{ .config = 0 xf00000, .period = 0 xf00000, .fd = FD_SUCCESS },
{ .config = 0 xf0ffff, .period = 0 xfffff0, .fd = FD_SUCCESS },
{ .config = 0 x1f0ffff, .period = 0 x1fffff0, .fd = FD_SUCCESS },
{ .config = 0 x7f0ffff, .period = 0 x7fffff0, .fd = FD_SUCCESS },
{ .config = 0 x8f0ffff, .period = -1 , .fd = FD_ERROR },
{ .config = 0 x17f0ffff, .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 = 0 xf, .ret = FD_ERROR, .period = -1 },
{ .freq = 0 , .sample_freq = 0 x10, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 0 , .sample_freq = 0 x11, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 0 , .sample_freq = 0 x8f, .ret = FD_SUCCESS, .period = 0 x80 },
{ .freq = 0 , .sample_freq = 0 x90, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 0 , .sample_freq = 0 x91, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 0 , .sample_freq = 0 x4d2, .ret = FD_SUCCESS, .period = 0 x4d0 },
{ .freq = 0 , .sample_freq = 0 x1007, .ret = FD_SUCCESS, .period = 0 x1000 },
{ .freq = 0 , .sample_freq = 0 xfff0, .ret = FD_SUCCESS, .period = 0 xfff0 },
{ .freq = 0 , .sample_freq = 0 xffff, .ret = FD_SUCCESS, .period = 0 xfff0 },
{ .freq = 0 , .sample_freq = 0 x10010, .ret = FD_SUCCESS, .period = 0 x10010 },
{ .freq = 0 , .sample_freq = 0 x7fffff, .ret = FD_SUCCESS, .period = 0 x7ffff0 },
{ .freq = 0 , .sample_freq = 0 xfffffff, .ret = FD_SUCCESS, .period = 0 xffffff0 },
{ .freq = 1 , .sample_freq = 0 , .ret = FD_ERROR, .period = -1 },
{ .freq = 1 , .sample_freq = 1 , .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 xf, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 x10, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 x11, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 x8f, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 x90, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 x91, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 x4d2, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 x1007, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 xfff0, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 xffff, .ret = FD_SUCCESS, .period = 0 x10 },
{ .freq = 1 , .sample_freq = 0 x10010, .ret = FD_SUCCESS, .period = 0 x10 },
/* ret=FD_ERROR because freq > default perf_event_max_sample_rate (100000) */
{ .freq = 1 , .sample_freq = 0 x7fffff, .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 = 0 xf, .ret = FD_ERROR, .period = -1 },
{ .freq = 0 , .sample_freq = 0 x10, .ret = FD_ERROR, .period = -1 },
{ .freq = 0 , .sample_freq = 0 x11, .ret = FD_ERROR, .period = -1 },
{ .freq = 0 , .sample_freq = 0 x8f, .ret = FD_ERROR, .period = -1 },
{ .freq = 0 , .sample_freq = 0 x90, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 0 , .sample_freq = 0 x91, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 0 , .sample_freq = 0 x4d2, .ret = FD_SUCCESS, .period = 0 x4d0 },
{ .freq = 0 , .sample_freq = 0 x1007, .ret = FD_SUCCESS, .period = 0 x1000 },
{ .freq = 0 , .sample_freq = 0 xfff0, .ret = FD_SUCCESS, .period = 0 xfff0 },
{ .freq = 0 , .sample_freq = 0 xffff, .ret = FD_SUCCESS, .period = 0 xfff0 },
{ .freq = 0 , .sample_freq = 0 x10010, .ret = FD_SUCCESS, .period = 0 x10010 },
{ .freq = 0 , .sample_freq = 0 x7fffff, .ret = FD_SUCCESS, .period = 0 x7ffff0 },
{ .freq = 0 , .sample_freq = 0 xfffffff, .ret = FD_SUCCESS, .period = 0 xffffff0 },
{ .freq = 1 , .sample_freq = 0 , .ret = FD_ERROR, .period = -1 },
{ .freq = 1 , .sample_freq = 1 , .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 xf, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 x10, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 x11, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 x8f, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 x90, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 x91, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 x4d2, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 x1007, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 xfff0, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 xffff, .ret = FD_SUCCESS, .period = 0 x90 },
{ .freq = 1 , .sample_freq = 0 x10010, .ret = FD_SUCCESS, .period = 0 x90 },
/* ret=FD_ERROR because freq > default perf_event_max_sample_rate (100000) */
{ .freq = 1 , .sample_freq = 0 x7fffff, .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 = 0 x0, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x1, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 xf, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x10, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 x11, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x1f, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x20, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 x80, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 x8f, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x90, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 x91, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x100, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 xfff0, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 xffff, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x10000, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 x1fff0, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 x1fff5, .ret = FD_ERROR },
{ .freq = 1 , .period = 0 x0, .ret = FD_ERROR },
{ .freq = 1 , .period = 0 x1, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 xf, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x10, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x11, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x1f, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x20, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x80, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x8f, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x90, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x91, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x100, .ret = FD_SUCCESS },
};
struct ibs_ioctl op_ioctl[] = {
{ .freq = 0 , .period = 0 x0, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x1, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 xf, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x10, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x11, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x1f, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x20, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x80, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x8f, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x90, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 x91, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x100, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 xfff0, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 xffff, .ret = FD_ERROR },
{ .freq = 0 , .period = 0 x10000, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 x1fff0, .ret = FD_SUCCESS },
{ .freq = 0 , .period = 0 x1fff5, .ret = FD_ERROR },
{ .freq = 1 , .period = 0 x0, .ret = FD_ERROR },
{ .freq = 1 , .period = 0 x1, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 xf, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x10, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x11, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x1f, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x20, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x80, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x8f, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x90, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x91, .ret = FD_SUCCESS },
{ .freq = 1 , .period = 0 x100, .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 : 0 x300D40 = = > MaxCnt : 200000
*/
op_prepare_attr(&attr, 0 x300D40, 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 = 0 x10,
};
struct ibs_l3missonly op_l3missonly = {
.freq = 1 ,
.sample_freq = 10000 ,
.ret = FD_SUCCESS,
.min_period = 0 x90,
};
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, 0 x800000000000000UL, l3missonly->freq,
l3missonly->sample_freq);
} else {
op_prepare_attr(&attr, 0 x10000, 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 0 x10 . 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;
}
Messung V0.5 in Prozent C=96 H=93 G=94
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-10-12)
¤
*© Formatika GbR, Deutschland