#define pr_fmt(fmt) "cpum_cf: " fmt
#include <linux/kernel.h>
#include <linux/kernel_stat.h>
#include <linux/percpu.h>
#include <linux/notifier.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/perf_event.h>
#include <asm/cpu_mf.h>
#include <asm/hwctrset.h>
#include <asm/debug.h>
#define PERF_CPUM_CF_MAX_CTR 0xffffUL
#define PERF_EVENT_CPUM_CF_DIAG 0xBC000UL
enum cpumf_ctr_set {
CPUMF_CTR_SET_BASIC = 0,
CPUMF_CTR_SET_USER = 1,
CPUMF_CTR_SET_CRYPTO = 2,
CPUMF_CTR_SET_EXT = 3,
CPUMF_CTR_SET_MT_DIAG = 4,
CPUMF_CTR_SET_MAX,
};
#define CPUMF_LCCTL_ENABLE_SHIFT 16
#define CPUMF_LCCTL_ACTCTL_SHIFT 0
static inline void ctr_set_enable(u64 *state, u64 ctrsets)
{
*state |= ctrsets << CPUMF_LCCTL_ENABLE_SHIFT;
}
static inline void ctr_set_disable(u64 *state, u64 ctrsets)
{
*state &= ~(ctrsets << CPUMF_LCCTL_ENABLE_SHIFT);
}
static inline void ctr_set_start(u64 *state, u64 ctrsets)
{
*state |= ctrsets << CPUMF_LCCTL_ACTCTL_SHIFT;
}
static inline void ctr_set_stop(u64 *state, u64 ctrsets)
{
*state &= ~(ctrsets << CPUMF_LCCTL_ACTCTL_SHIFT);
}
static inline int ctr_stcctm(enum cpumf_ctr_set set, u64 range, u64 *dest)
{
switch (set) {
case CPUMF_CTR_SET_BASIC:
return stcctm(BASIC, range, dest);
case CPUMF_CTR_SET_USER:
return stcctm(PROBLEM_STATE, range, dest);
case CPUMF_CTR_SET_CRYPTO:
return stcctm(CRYPTO_ACTIVITY, range, dest);
case CPUMF_CTR_SET_EXT:
return stcctm(EXTENDED, range, dest);
case CPUMF_CTR_SET_MT_DIAG:
return stcctm(MT_DIAG_CLEARING, range, dest);
case CPUMF_CTR_SET_MAX:
return 3;
}
return 3;
}
struct cpu_cf_events {
refcount_t refcnt;
atomic_t ctr_set[CPUMF_CTR_SET_MAX];
u64 state;
u64 dev_state;
unsigned int flags;
size_t used;
size_t usedss;
unsigned char start[PAGE_SIZE];
unsigned char stop[PAGE_SIZE];
unsigned char data[PAGE_SIZE];
unsigned int sets;
};
static unsigned int cfdiag_cpu_speed;
static debug_info_t *cf_dbg;
static struct cpumf_ctr_info cpumf_ctr_info;
struct cpu_cf_ptr {
struct cpu_cf_events *cpucf;
};
static struct cpu_cf_root {
refcount_t refcnt;
struct cpu_cf_ptr __percpu *cfptr;
} cpu_cf_root;
static DEFINE_MUTEX(pmc_reserve_mutex);
static struct cpu_cf_events *get_cpu_cfhw(int cpu)
{
struct cpu_cf_ptr __percpu *p = cpu_cf_root.cfptr;
if (p) {
struct cpu_cf_ptr *q = per_cpu_ptr(p, cpu);
return q->cpucf;
}
return NULL;
}
static struct cpu_cf_events *this_cpu_cfhw(void)
{
return get_cpu_cfhw(smp_processor_id());
}
static void cpum_cf_reset_cpu(void *flags)
{
lcctl(0);
}
static void cpum_cf_free_root(void)
{
if (!refcount_dec_and_test(&cpu_cf_root.refcnt))
return;
free_percpu(cpu_cf_root.cfptr);
cpu_cf_root.cfptr = NULL;
irq_subclass_unregister(IRQ_SUBCLASS_MEASUREMENT_ALERT);
on_each_cpu(cpum_cf_reset_cpu, NULL, 1);
debug_sprintf_event(cf_dbg, 4, "%s root.refcnt %u cfptr %d\n",
__func__, refcount_read(&cpu_cf_root.refcnt),
!cpu_cf_root.cfptr);
}
static int cpum_cf_alloc_root(void)
{
int rc = 0;
if (refcount_inc_not_zero(&cpu_cf_root.refcnt))
return rc;
cpu_cf_root.cfptr = alloc_percpu(struct cpu_cf_ptr);
if (cpu_cf_root.cfptr) {
refcount_set(&cpu_cf_root.refcnt, 1);
on_each_cpu(cpum_cf_reset_cpu, NULL, 1);
irq_subclass_register(IRQ_SUBCLASS_MEASUREMENT_ALERT);
} else {
rc = -ENOMEM;
}
return rc;
}
static void cpum_cf_free_cpu(int cpu)
{
struct cpu_cf_events *cpuhw;
struct cpu_cf_ptr *p;
mutex_lock(&pmc_reserve_mutex);
if (!cpu_cf_root.cfptr)
goto out;
p = per_cpu_ptr(cpu_cf_root.cfptr, cpu);
cpuhw = p->cpucf;
if (!cpuhw)
goto out;
if (refcount_dec_and_test(&cpuhw->refcnt)) {
kfree(cpuhw);
p->cpucf = NULL;
}
cpum_cf_free_root();
out:
mutex_unlock(&pmc_reserve_mutex);
}
static int cpum_cf_alloc_cpu(int cpu)
{
struct cpu_cf_events *cpuhw;
struct cpu_cf_ptr *p;
int rc;
mutex_lock(&pmc_reserve_mutex);
rc = cpum_cf_alloc_root();
if (rc)
goto unlock;
p = per_cpu_ptr(cpu_cf_root.cfptr, cpu);
cpuhw = p->cpucf;
if (!cpuhw) {
cpuhw = kzalloc(sizeof(*cpuhw), GFP_KERNEL);
if (cpuhw) {
p->cpucf = cpuhw;
refcount_set(&cpuhw->refcnt, 1);
} else {
rc = -ENOMEM;
}
} else {
refcount_inc(&cpuhw->refcnt);
}
if (rc) {
cpum_cf_free_root();
}
unlock:
mutex_unlock(&pmc_reserve_mutex);
return rc;
}
static int cpum_cf_alloc(int cpu)
{
cpumask_var_t mask;
int rc;
if (cpu == -1) {
if (!zalloc_cpumask_var(&mask, GFP_KERNEL))
return -ENOMEM;
for_each_online_cpu(cpu) {
rc = cpum_cf_alloc_cpu(cpu);
if (rc) {
for_each_cpu(cpu, mask)
cpum_cf_free_cpu(cpu);
break;
}
cpumask_set_cpu(cpu, mask);
}
free_cpumask_var(mask);
} else {
rc = cpum_cf_alloc_cpu(cpu);
}
return rc;
}
static void cpum_cf_free(int cpu)
{
if (cpu == -1) {
for_each_online_cpu(cpu)
cpum_cf_free_cpu(cpu);
} else {
cpum_cf_free_cpu(cpu);
}
}
#define CF_DIAG_CTRSET_DEF 0xfeef
struct cf_ctrset_entry {
unsigned int def:16;
unsigned int set:16;
unsigned int ctr:16;
unsigned int res1:16;
};
struct cf_trailer_entry {
union {
struct {
unsigned int clock_base:1;
unsigned int speed:1;
unsigned int mtda:1;
unsigned int caca:1;
unsigned int lcda:1;
};
unsigned long flags;
};
unsigned int cfvn:16;
unsigned int csvn:16;
unsigned int cpu_speed:32;
unsigned long timestamp;
union {
struct {
unsigned long progusage1;
unsigned long progusage2;
unsigned long progusage3;
unsigned long tod_base;
};
unsigned long progusage[4];
};
unsigned int mach_type:16;
unsigned int res1:16;
unsigned int res2:32;
};
static void cfdiag_trailer(struct cf_trailer_entry *te)
{
struct cpuid cpuid;
te->cfvn = cpumf_ctr_info.cfvn;
te->csvn = cpumf_ctr_info.csvn;
get_cpu_id(&cpuid);
te->mach_type = cpuid.machine;
te->cpu_speed = cfdiag_cpu_speed;
if (te->cpu_speed)
te->speed = 1;
te->clock_base = 1;
te->tod_base = tod_clock_base.tod;
te->timestamp = get_tod_clock_fast();
}
static size_t cpumf_ctr_setsizes[CPUMF_CTR_SET_MAX];
static void cpum_cf_make_setsize(enum cpumf_ctr_set ctrset)
{
size_t ctrset_size = 0;
switch (ctrset) {
case CPUMF_CTR_SET_BASIC:
if (cpumf_ctr_info.cfvn >= 1)
ctrset_size = 6;
break;
case CPUMF_CTR_SET_USER:
if (cpumf_ctr_info.cfvn == 1)
ctrset_size = 6;
else if (cpumf_ctr_info.cfvn >= 3)
ctrset_size = 2;
break;
case CPUMF_CTR_SET_CRYPTO:
if (cpumf_ctr_info.csvn >= 1 && cpumf_ctr_info.csvn <= 5)
ctrset_size = 16;
else if (cpumf_ctr_info.csvn >= 6)
ctrset_size = 20;
break;
case CPUMF_CTR_SET_EXT:
if (cpumf_ctr_info.csvn == 1)
ctrset_size = 32;
else if (cpumf_ctr_info.csvn == 2)
ctrset_size = 48;
else if (cpumf_ctr_info.csvn >= 3 && cpumf_ctr_info.csvn <= 5)
ctrset_size = 128;
else if (cpumf_ctr_info.csvn >= 6 && cpumf_ctr_info.csvn <= 8)
ctrset_size = 160;
break;
case CPUMF_CTR_SET_MT_DIAG:
if (cpumf_ctr_info.csvn > 3)
ctrset_size = 48;
break;
case CPUMF_CTR_SET_MAX:
break;
}
cpumf_ctr_setsizes[ctrset] = ctrset_size;
}
static size_t cpum_cf_read_setsize(enum cpumf_ctr_set ctrset)
{
return cpumf_ctr_setsizes[ctrset];
}
static size_t cfdiag_getctrset(struct cf_ctrset_entry *ctrdata, int ctrset,
size_t room, bool error_ok)
{
size_t ctrset_size, need = 0;
int rc = 3;
ctrdata->def = CF_DIAG_CTRSET_DEF;
ctrdata->set = ctrset;
ctrdata->res1 = 0;
ctrset_size = cpum_cf_read_setsize(ctrset);
if (ctrset_size) {
need = ctrset_size * sizeof(u64) + sizeof(*ctrdata);
if (need <= room) {
rc = ctr_stcctm(ctrset, ctrset_size,
(u64 *)(ctrdata + 1));
}
if (rc != 3 || error_ok)
ctrdata->ctr = ctrset_size;
else
need = 0;
}
return need;
}
static const u64 cpumf_ctr_ctl[CPUMF_CTR_SET_MAX] = {
[CPUMF_CTR_SET_BASIC] = 0x02,
[CPUMF_CTR_SET_USER] = 0x04,
[CPUMF_CTR_SET_CRYPTO] = 0x08,
[CPUMF_CTR_SET_EXT] = 0x01,
[CPUMF_CTR_SET_MT_DIAG] = 0x20,
};
static size_t cfdiag_getctr(void *data, size_t sz, unsigned long auth,
bool error_ok)
{
struct cf_trailer_entry *trailer;
size_t offset = 0, done;
int i;
memset(data, 0, sz);
sz -= sizeof(*trailer);
for (i = CPUMF_CTR_SET_BASIC; i < CPUMF_CTR_SET_MAX; ++i) {
struct cf_ctrset_entry *ctrdata = data + offset;
if (!(auth & cpumf_ctr_ctl[i]))
continue;
done = cfdiag_getctrset(ctrdata, i, sz - offset, error_ok);
offset += done;
}
trailer = data + offset;
cfdiag_trailer(trailer);
return offset + sizeof(*trailer);
}
static void cfdiag_diffctrset(u64 *pstart, u64 *pstop, int counters)
{
for (; --counters >= 0; ++pstart, ++pstop)
if (*pstop >= *pstart)
*pstop -= *pstart;
else
*pstop = *pstart - *pstop + 1;
}
static int cfdiag_diffctr(struct cpu_cf_events *cpuhw, unsigned long auth)
{
struct cf_trailer_entry *trailer_start, *trailer_stop;
struct cf_ctrset_entry *ctrstart, *ctrstop;
size_t offset = 0;
int i;
for (i = CPUMF_CTR_SET_BASIC; i < CPUMF_CTR_SET_MAX; ++i) {
ctrstart = (struct cf_ctrset_entry *)(cpuhw->start + offset);
ctrstop = (struct cf_ctrset_entry *)(cpuhw->stop + offset);
if (!(auth & cpumf_ctr_ctl[i]))
continue;
if (!cpum_cf_read_setsize(i))
continue;
if (memcmp(ctrstop, ctrstart, sizeof(*ctrstop))) {
pr_err_once("cpum_cf_diag counter set compare error "
"in set %i\n", ctrstart->set);
return 0;
}
if (ctrstart->def == CF_DIAG_CTRSET_DEF) {
cfdiag_diffctrset((u64 *)(ctrstart + 1),
(u64 *)(ctrstop + 1), ctrstart->ctr);
offset += ctrstart->ctr * sizeof(u64) +
sizeof(*ctrstart);
}
}
trailer_start = (struct cf_trailer_entry *)(cpuhw->start + offset);
trailer_stop = (struct cf_trailer_entry *)(cpuhw->stop + offset);
trailer_stop->progusage[0] = trailer_start->timestamp;
return 1;
}
static enum cpumf_ctr_set get_counter_set(u64 event)
{
int set = CPUMF_CTR_SET_MAX;
if (event < 32)
set = CPUMF_CTR_SET_BASIC;
else if (event < 64)
set = CPUMF_CTR_SET_USER;
else if (event < 128)
set = CPUMF_CTR_SET_CRYPTO;
else if (event < 288)
set = CPUMF_CTR_SET_EXT;
else if (event >= 448 && event < 496)
set = CPUMF_CTR_SET_MT_DIAG;
return set;
}
static int validate_ctr_version(const u64 config, enum cpumf_ctr_set set)
{
u16 mtdiag_ctl;
int err = 0;
switch (set) {
case CPUMF_CTR_SET_BASIC:
case CPUMF_CTR_SET_USER:
if (cpumf_ctr_info.cfvn < 1)
err = -EOPNOTSUPP;
break;
case CPUMF_CTR_SET_CRYPTO:
if ((cpumf_ctr_info.csvn >= 1 && cpumf_ctr_info.csvn <= 5 &&
config > 79) || (cpumf_ctr_info.csvn >= 6 && config > 83))
err = -EOPNOTSUPP;
break;
case CPUMF_CTR_SET_EXT:
if (cpumf_ctr_info.csvn < 1)
err = -EOPNOTSUPP;
if ((cpumf_ctr_info.csvn == 1 && config > 159) ||
(cpumf_ctr_info.csvn == 2 && config > 175) ||
(cpumf_ctr_info.csvn >= 3 && cpumf_ctr_info.csvn <= 5 &&
config > 255) ||
(cpumf_ctr_info.csvn >= 6 && config > 287))
err = -EOPNOTSUPP;
break;
case CPUMF_CTR_SET_MT_DIAG:
if (cpumf_ctr_info.csvn <= 3)
err = -EOPNOTSUPP;
mtdiag_ctl = cpumf_ctr_ctl[CPUMF_CTR_SET_MT_DIAG];
if (!((cpumf_ctr_info.auth_ctl & mtdiag_ctl) &&
(cpumf_ctr_info.enable_ctl & mtdiag_ctl) &&
(cpumf_ctr_info.act_ctl & mtdiag_ctl)))
err = -EOPNOTSUPP;
break;
case CPUMF_CTR_SET_MAX:
err = -EOPNOTSUPP;
}
return err;
}
static void cpumf_pmu_enable(struct pmu *pmu)
{
struct cpu_cf_events *cpuhw = this_cpu_cfhw();
int err;
if (!cpuhw || (cpuhw->flags & PMU_F_ENABLED))
return;
err = lcctl(cpuhw->state | cpuhw->dev_state);
if (err)
pr_err("Enabling the performance measuring unit failed with rc=%x\n", err);
else
cpuhw->flags |= PMU_F_ENABLED;
}
static void cpumf_pmu_disable(struct pmu *pmu)
{
struct cpu_cf_events *cpuhw = this_cpu_cfhw();
u64 inactive;
int err;
if (!cpuhw || !(cpuhw->flags & PMU_F_ENABLED))
return;
inactive = cpuhw->state & ~((1 << CPUMF_LCCTL_ENABLE_SHIFT) - 1);
inactive |= cpuhw->dev_state;
err = lcctl(inactive);
if (err)
pr_err("Disabling the performance measuring unit failed with rc=%x\n", err);
else
cpuhw->flags &= ~PMU_F_ENABLED;
}
static void hw_perf_event_destroy(struct perf_event *event)
{
cpum_cf_free(event->cpu);
}
static const int cpumf_generic_events_basic[] = {
[PERF_COUNT_HW_CPU_CYCLES] = 0,
[PERF_COUNT_HW_INSTRUCTIONS] = 1,
[PERF_COUNT_HW_CACHE_REFERENCES] = -1,
[PERF_COUNT_HW_CACHE_MISSES] = -1,
[PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = -1,
[PERF_COUNT_HW_BRANCH_MISSES] = -1,
[PERF_COUNT_HW_BUS_CYCLES] = -1,
};
static const int cpumf_generic_events_user[] = {
[PERF_COUNT_HW_CPU_CYCLES] = 32,
[PERF_COUNT_HW_INSTRUCTIONS] = 33,
[PERF_COUNT_HW_CACHE_REFERENCES] = -1,
[PERF_COUNT_HW_CACHE_MISSES] = -1,
[PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = -1,
[PERF_COUNT_HW_BRANCH_MISSES] = -1,
[PERF_COUNT_HW_BUS_CYCLES] = -1,
};
static int is_userspace_event(u64 ev)
{
return cpumf_generic_events_user[PERF_COUNT_HW_CPU_CYCLES] == ev ||
cpumf_generic_events_user[PERF_COUNT_HW_INSTRUCTIONS] == ev;
}
static int __hw_perf_event_init(struct perf_event *event, unsigned int type)
{
struct perf_event_attr *attr = &event->attr;
struct hw_perf_event *hwc = &event->hw;
enum cpumf_ctr_set set;
u64 ev;
switch (type) {
case PERF_TYPE_RAW:
if (attr->exclude_kernel || attr->exclude_user ||
attr->exclude_hv)
return -EOPNOTSUPP;
ev = attr->config;
break;
case PERF_TYPE_HARDWARE:
ev = attr->config;
if (!attr->exclude_user && attr->exclude_kernel) {
if (!is_userspace_event(ev)) {
if (ev >= ARRAY_SIZE(cpumf_generic_events_user))
return -EOPNOTSUPP;
ev = cpumf_generic_events_user[ev];
}
} else if (!attr->exclude_kernel && attr->exclude_user) {
return -EOPNOTSUPP;
} else {
if (!is_userspace_event(ev)) {
if (ev >= ARRAY_SIZE(cpumf_generic_events_basic))
return -EOPNOTSUPP;
ev = cpumf_generic_events_basic[ev];
}
}
break;
default:
return -ENOENT;
}
if (ev == -1)
return -ENOENT;
if (ev > PERF_CPUM_CF_MAX_CTR)
return -ENOENT;
set = get_counter_set(ev);
switch (set) {
case CPUMF_CTR_SET_BASIC:
case CPUMF_CTR_SET_USER:
case CPUMF_CTR_SET_CRYPTO:
case CPUMF_CTR_SET_EXT:
case CPUMF_CTR_SET_MT_DIAG:
hwc->config = ev;
hwc->config_base = cpumf_ctr_ctl[set];
break;
case CPUMF_CTR_SET_MAX:
return -EINVAL;
}
if (cpum_cf_alloc(event->cpu))
return -ENOMEM;
event->destroy = hw_perf_event_destroy;
if (!(hwc->config_base & cpumf_ctr_info.auth_ctl))
return -ENOENT;
return validate_ctr_version(hwc->config, set);
}
static int cpumf_pmu_event_type(struct perf_event *event)
{
u64 ev = event->attr.config;
if (cpumf_generic_events_basic[PERF_COUNT_HW_CPU_CYCLES] == ev ||
cpumf_generic_events_basic[PERF_COUNT_HW_INSTRUCTIONS] == ev ||
cpumf_generic_events_user[PERF_COUNT_HW_CPU_CYCLES] == ev ||
cpumf_generic_events_user[PERF_COUNT_HW_INSTRUCTIONS] == ev)
return PERF_TYPE_HARDWARE;
return PERF_TYPE_RAW;
}
static int cpumf_pmu_event_init(struct perf_event *event)
{
unsigned int type = event->attr.type;
int err = -ENOENT;
if (is_sampling_event(event))
return err;
if (type == PERF_TYPE_HARDWARE || type == PERF_TYPE_RAW)
err = __hw_perf_event_init(event, type);
else if (event->pmu->type == type)
err = __hw_perf_event_init(event, cpumf_pmu_event_type(event));
return err;
}
static int hw_perf_event_reset(struct perf_event *event)
{
u64 prev, new;
int err;
prev = local64_read(&event->hw.prev_count);
do {
err = ecctr(event->hw.config, &new);
if (err) {
if (err != 3)
break;
new = 0;
}
} while (!local64_try_cmpxchg(&event->hw.prev_count, &prev, new));
return err;
}
static void hw_perf_event_update(struct perf_event *event)
{
u64 prev, new, delta;
int err;
prev = local64_read(&event->hw.prev_count);
do {
err = ecctr(event->hw.config, &new);
if (err)
return;
} while (!local64_try_cmpxchg(&event->hw.prev_count, &prev, new));
delta = (prev <= new) ? new - prev
: (-1ULL - prev) + new + 1;
local64_add(delta, &event->count);
}
static void cpumf_pmu_read(struct perf_event *event)
{
if (event->hw.state & PERF_HES_STOPPED)
return;
hw_perf_event_update(event);
}
static void cpumf_pmu_start(struct perf_event *event, int flags)
{
struct cpu_cf_events *cpuhw = this_cpu_cfhw();
struct hw_perf_event *hwc = &event->hw;
int i;
if (!(hwc->state & PERF_HES_STOPPED))
return;
hwc->state = 0;
ctr_set_enable(&cpuhw->state, hwc->config_base);
ctr_set_start(&cpuhw->state, hwc->config_base);
if (hwc->config == PERF_EVENT_CPUM_CF_DIAG) {
cpuhw->usedss = cfdiag_getctr(cpuhw->start,
sizeof(cpuhw->start),
hwc->config_base, true);
} else {
hw_perf_event_reset(event);
}
for (i = CPUMF_CTR_SET_BASIC; i < CPUMF_CTR_SET_MAX; ++i)
if ((hwc->config_base & cpumf_ctr_ctl[i]))
atomic_inc(&cpuhw->ctr_set[i]);
}
static int cfdiag_push_sample(struct perf_event *event,
struct cpu_cf_events *cpuhw)
{
struct perf_sample_data data;
struct perf_raw_record raw;
struct pt_regs regs;
int overflow;
perf_sample_data_init(&data, 0, event->hw.last_period);
memset(®s, 0, sizeof(regs));
memset(&raw, 0, sizeof(raw));
if (event->attr.sample_type & PERF_SAMPLE_CPU)
data.cpu_entry.cpu = event->cpu;
if (event->attr.sample_type & PERF_SAMPLE_RAW) {
raw.frag.size = cpuhw->usedss;
raw.frag.data = cpuhw->stop;
perf_sample_save_raw_data(&data, event, &raw);
}
overflow = perf_event_overflow(event, &data, ®s);
perf_event_update_userpage(event);
return overflow;
}
static void cpumf_pmu_stop(struct perf_event *event, int flags)
{
struct cpu_cf_events *cpuhw = this_cpu_cfhw();
struct hw_perf_event *hwc = &event->hw;
int i;
if (!(hwc->state & PERF_HES_STOPPED)) {
for (i = CPUMF_CTR_SET_BASIC; i < CPUMF_CTR_SET_MAX; ++i) {
if (!(hwc->config_base & cpumf_ctr_ctl[i]))
continue;
if (!atomic_dec_return(&cpuhw->ctr_set[i]))
ctr_set_stop(&cpuhw->state, cpumf_ctr_ctl[i]);
}
hwc->state |= PERF_HES_STOPPED;
}
if ((flags & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) {
if (hwc->config == PERF_EVENT_CPUM_CF_DIAG) {
local64_inc(&event->count);
cpuhw->usedss = cfdiag_getctr(cpuhw->stop,
sizeof(cpuhw->stop),
event->hw.config_base,
false);
if (cfdiag_diffctr(cpuhw, event->hw.config_base))
cfdiag_push_sample(event, cpuhw);
} else {
hw_perf_event_update(event);
}
hwc->state |= PERF_HES_UPTODATE;
}
}
static int cpumf_pmu_add(struct perf_event *event, int flags)
{
struct cpu_cf_events *cpuhw = this_cpu_cfhw();
ctr_set_enable(&cpuhw->state, event->hw.config_base);
event->hw.state = PERF_HES_UPTODATE | PERF_HES_STOPPED;
if (flags & PERF_EF_START)
cpumf_pmu_start(event, PERF_EF_RELOAD);
return 0;
}
static void cpumf_pmu_del(struct perf_event *event, int flags)
{
struct cpu_cf_events *cpuhw = this_cpu_cfhw();
int i;
cpumf_pmu_stop(event, PERF_EF_UPDATE);
for (i = CPUMF_CTR_SET_BASIC; i < CPUMF_CTR_SET_MAX; ++i)
if (!atomic_read(&cpuhw->ctr_set[i]))
ctr_set_disable(&cpuhw->state, cpumf_ctr_ctl[i]);
}
static struct pmu cpumf_pmu = {
.task_ctx_nr = perf_sw_context,
.capabilities = PERF_PMU_CAP_NO_INTERRUPT,
.pmu_enable = cpumf_pmu_enable,
.pmu_disable = cpumf_pmu_disable,
.event_init = cpumf_pmu_event_init,
.add = cpumf_pmu_add,
.del = cpumf_pmu_del,
.start = cpumf_pmu_start,
.stop = cpumf_pmu_stop,
.read = cpumf_pmu_read,
};
static struct cfset_session {
struct list_head head;
} cfset_session = {
.head = LIST_HEAD_INIT(cfset_session.head)
};
static refcount_t cfset_opencnt = REFCOUNT_INIT(0);
static DEFINE_MUTEX(cfset_ctrset_mutex);
static int cfset_online_cpu(unsigned int cpu);
static int cpum_cf_online_cpu(unsigned int cpu)
{
int rc = 0;
mutex_lock(&cfset_ctrset_mutex);
if (refcount_read(&cfset_opencnt)) {
rc = cpum_cf_alloc_cpu(cpu);
if (!rc)
cfset_online_cpu(cpu);
}
mutex_unlock(&cfset_ctrset_mutex);
return rc;
}
static int cfset_offline_cpu(unsigned int cpu);
static int cpum_cf_offline_cpu(unsigned int cpu)
{
mutex_lock(&cfset_ctrset_mutex);
if (refcount_read(&cfset_opencnt)) {
cfset_offline_cpu(cpu);
cpum_cf_free_cpu(cpu);
}
mutex_unlock(&cfset_ctrset_mutex);
return 0;
}
static inline int stccm_avail(void)
{
return test_facility(142);
}
static void cpumf_measurement_alert(struct ext_code ext_code,
unsigned int alert, unsigned long unused)
{
struct cpu_cf_events *cpuhw;
if (!(alert & CPU_MF_INT_CF_MASK))
return;
inc_irq_stat(IRQEXT_CMC);
cpuhw = this_cpu_cfhw();
if (!cpuhw)
return;
if (alert & CPU_MF_INT_CF_CACA)
qctri(&cpumf_ctr_info);
if (alert & CPU_MF_INT_CF_LCDA)
pr_err("CPU[%i] Counter data was lost\n", smp_processor_id());
if (alert & CPU_MF_INT_CF_MTDA)
pr_warn("CPU[%i] MT counter data was lost\n",
smp_processor_id());
}
static int cfset_init(void);
static int __init cpumf_pmu_init(void)
{
int rc;
if (!cpum_cf_avail() || qctri(&cpumf_ctr_info))
return -ENODEV;
for (rc = CPUMF_CTR_SET_BASIC; rc < CPUMF_CTR_SET_MAX; ++rc)
cpum_cf_make_setsize(rc);
system_ctl_clear_bit(0, CR0_CPUMF_EXTRACTION_AUTH_BIT);
rc = register_external_irq(EXT_IRQ_MEASURE_ALERT,
cpumf_measurement_alert);
if (rc) {
pr_err("Registering for CPU-measurement alerts failed with rc=%i\n", rc);
return rc;
}
cf_dbg = debug_register("cpum_cf", 2, 1, 128);
if (!cf_dbg) {
pr_err("Registration of s390dbf(cpum_cf) failed\n");
rc = -ENOMEM;
goto out1;
}
debug_register_view(cf_dbg, &debug_sprintf_view);
cpumf_pmu.attr_groups = cpumf_cf_event_group();
rc = perf_pmu_register(&cpumf_pmu, "cpum_cf", -1);
if (rc) {
pr_err("Registering the cpum_cf PMU failed with rc=%i\n", rc);
goto out2;
} else if (stccm_avail()) {
cfset_init();
}
rc = cpuhp_setup_state(CPUHP_AP_PERF_S390_CF_ONLINE,
"perf/s390/cf:online",
cpum_cf_online_cpu, cpum_cf_offline_cpu);
return rc;
out2:
debug_unregister_view(cf_dbg, &debug_sprintf_view);
debug_unregister(cf_dbg);
out1:
unregister_external_irq(EXT_IRQ_MEASURE_ALERT, cpumf_measurement_alert);
return rc;
}
struct cfset_call_on_cpu_parm {
unsigned int sets;
atomic_t cpus_ack;
};
struct cfset_request {
unsigned long ctrset;
cpumask_t mask;
struct list_head node;
};
static void cfset_session_init(void)
{
INIT_LIST_HEAD(&cfset_session.head);
}
static void cfset_session_del(struct cfset_request *p)
{
list_del(&p->node);
}
static void cfset_session_add(struct cfset_request *p)
{
list_add(&p->node, &cfset_session.head);
}
static void cfset_ioctl_off(void *parm)
{
struct cpu_cf_events *cpuhw = this_cpu_cfhw();
struct cfset_call_on_cpu_parm *p = parm;
int rc;
for (rc = CPUMF_CTR_SET_BASIC; rc < CPUMF_CTR_SET_MAX; ++rc)
if ((p->sets & cpumf_ctr_ctl[rc])) {
if (!atomic_dec_return(&cpuhw->ctr_set[rc])) {
ctr_set_disable(&cpuhw->dev_state,
cpumf_ctr_ctl[rc]);
ctr_set_stop(&cpuhw->dev_state,
cpumf_ctr_ctl[rc]);
}
}
rc = lcctl(cpuhw->dev_state | cpuhw->state);
if (rc)
pr_err("Counter set stop %#llx of /dev/%s failed rc=%i\n",
cpuhw->state, S390_HWCTR_DEVICE, rc);
if (!cpuhw->dev_state)
cpuhw->flags &= ~PMU_F_IN_USE;
}
static void cfset_ioctl_on(void *parm)
{
struct cpu_cf_events *cpuhw = this_cpu_cfhw();
struct cfset_call_on_cpu_parm *p = parm;
int rc;
cpuhw->flags |= PMU_F_IN_USE;
ctr_set_enable(&cpuhw->dev_state, p->sets);
ctr_set_start(&cpuhw->dev_state, p->sets);
for (rc = CPUMF_CTR_SET_BASIC; rc < CPUMF_CTR_SET_MAX; ++rc)
if ((p->sets & cpumf_ctr_ctl[rc]))
atomic_inc(&cpuhw->ctr_set[rc]);
rc = lcctl(cpuhw->dev_state | cpuhw->state);
if (!rc)
atomic_inc(&p->cpus_ack);
else
pr_err("Counter set start %#llx of /dev/%s failed rc=%i\n",
cpuhw->dev_state | cpuhw->state, S390_HWCTR_DEVICE, rc);
}
static void cfset_release_cpu(void *p)
{
struct cpu_cf_events *cpuhw = this_cpu_cfhw();
int rc;
cpuhw->dev_state = 0;
rc = lcctl(cpuhw->state);
if (rc)
pr_err("Counter set release %#llx of /dev/%s failed rc=%i\n",
cpuhw->state, S390_HWCTR_DEVICE, rc);
}
static void cfset_all_stop(struct cfset_request *req)
{
struct cfset_call_on_cpu_parm p = {
.sets = req->ctrset,
};
cpumask_and(&req->mask, &req->mask, cpu_online_mask);
on_each_cpu_mask(&req->mask, cfset_ioctl_off, &p, 1);
}
static int cfset_release(struct inode *inode, struct file *file)
{
mutex_lock(&cfset_ctrset_mutex);
if (file->private_data) {
cfset_all_stop(file->private_data);
cfset_session_del(file->private_data);
kfree(file->private_data);
file->private_data = NULL;
}
if (refcount_dec_and_test(&cfset_opencnt)) {
on_each_cpu(cfset_release_cpu, NULL, 1);
cpum_cf_free(-1);
}
mutex_unlock(&cfset_ctrset_mutex);
return 0;
}
static int cfset_open(struct inode *inode, struct file *file)
{
int rc = 0;
if (!perfmon_capable())
return -EPERM;
file->private_data = NULL;
mutex_lock(&cfset_ctrset_mutex);
if (!refcount_inc_not_zero(&cfset_opencnt)) {
rc = cpum_cf_alloc(-1);
if (!rc) {
cfset_session_init();
refcount_set(&cfset_opencnt, 1);
}
}
mutex_unlock(&cfset_ctrset_mutex);
return rc ?: nonseekable_open(inode, file);
}
static int cfset_all_start(struct cfset_request *req)
{
struct cfset_call_on_cpu_parm p = {
.sets = req->ctrset,
.cpus_ack = ATOMIC_INIT(0),
};
cpumask_var_t mask;
int rc = 0;
if (!alloc_cpumask_var(&mask, GFP_KERNEL))
return -ENOMEM;
cpumask_and(mask, &req->mask, cpu_online_mask);
on_each_cpu_mask(mask, cfset_ioctl_on, &p, 1);
if (atomic_read(&p.cpus_ack) != cpumask_weight(mask)) {
on_each_cpu_mask(mask, cfset_ioctl_off, &p, 1);
rc = -EIO;
}
free_cpumask_var(mask);
return rc;
}
static size_t cfset_needspace(unsigned int sets)
{
size_t bytes = 0;
int i;
for (i = CPUMF_CTR_SET_BASIC; i < CPUMF_CTR_SET_MAX; ++i) {
if (!(sets & cpumf_ctr_ctl[i]))
continue;
bytes += cpum_cf_read_setsize(i) * sizeof(u64) +
sizeof(((struct s390_ctrset_setdata *)0)->set) +
sizeof(((struct s390_ctrset_setdata *)0)->no_cnts);
}
bytes = sizeof(((struct s390_ctrset_read *)0)->no_cpus) + nr_cpu_ids *
(bytes + sizeof(((struct s390_ctrset_cpudata *)0)->cpu_nr) +
sizeof(((struct s390_ctrset_cpudata *)0)->no_sets));
return bytes;
}
static int cfset_all_copy(unsigned long arg, cpumask_t *mask)
{
struct s390_ctrset_read __user *ctrset_read;
unsigned int cpu, cpus, rc = 0;
void __user *uptr;
ctrset_read = (struct s390_ctrset_read __user *)arg;
uptr = ctrset_read->data;
for_each_cpu(cpu, mask) {
struct cpu_cf_events *cpuhw = get_cpu_cfhw(cpu);
struct s390_ctrset_cpudata __user *ctrset_cpudata;
ctrset_cpudata = uptr;
rc = put_user(cpu, &ctrset_cpudata->cpu_nr);
rc |= put_user(cpuhw->sets, &ctrset_cpudata->no_sets);
rc |= copy_to_user(ctrset_cpudata->data, cpuhw->data,
cpuhw->used);
if (rc) {
rc = -EFAULT;
goto out;
}
uptr += sizeof(struct s390_ctrset_cpudata) + cpuhw->used;
cond_resched();
}
cpus = cpumask_weight(mask);
if (put_user(cpus, &ctrset_read->no_cpus))
rc = -EFAULT;
out:
return rc;
}
static size_t cfset_cpuset_read(struct s390_ctrset_setdata *p, int ctrset,
int ctrset_size, size_t room)
{
size_t need = 0;
int rc = -1;
need = sizeof(*p) + sizeof(u64) * ctrset_size;
if (need <= room) {
p->set = cpumf_ctr_ctl[ctrset];
p->no_cnts = ctrset_size;
rc = ctr_stcctm(ctrset, ctrset_size, (u64 *)p->cv);
if (rc == 3)
need = 0;
}
return need;
}
static void cfset_cpu_read(void *parm)
{
struct cpu_cf_events *cpuhw = this_cpu_cfhw();
struct cfset_call_on_cpu_parm *p = parm;
int set, set_size;
size_t space;
cpuhw->used = 0;
cpuhw->sets = 0;
memset(cpuhw->data, 0, sizeof(cpuhw->data));
for (set = CPUMF_CTR_SET_BASIC; set < CPUMF_CTR_SET_MAX; ++set) {
struct s390_ctrset_setdata *sp = (void *)cpuhw->data +
cpuhw->used;
if (!(p->sets & cpumf_ctr_ctl[set]))
continue;
set_size = cpum_cf_read_setsize(set);
space = sizeof(cpuhw->data) - cpuhw->used;
space = cfset_cpuset_read(sp, set, set_size, space);
if (space) {
cpuhw->used += space;
cpuhw->sets += 1;
}
}
}
static int cfset_all_read(unsigned long arg, struct cfset_request *req)
{
struct cfset_call_on_cpu_parm p;
cpumask_var_t mask;
int rc;
if (!alloc_cpumask_var(&mask, GFP_KERNEL))
return -ENOMEM;
p.sets = req->ctrset;
cpumask_and(mask, &req->mask, cpu_online_mask);
on_each_cpu_mask(mask, cfset_cpu_read, &p, 1);
rc = cfset_all_copy(arg, mask);
free_cpumask_var(mask);
return rc;
}
static long cfset_ioctl_read(unsigned long arg, struct cfset_request *req)
{
int ret = -ENODATA;
if (req && req->ctrset)
ret = cfset_all_read(arg, req);
return ret;
}
static long cfset_ioctl_stop(struct file *file)
{
struct cfset_request *req = file->private_data;
int ret = -ENXIO;
if (req) {
cfset_all_stop(req);
cfset_session_del(req);
kfree(req);
file->private_data = NULL;
ret = 0;
}
return ret;
}
static long cfset_ioctl_start(unsigned long arg, struct file *file)
{
struct s390_ctrset_start __user *ustart;
struct s390_ctrset_start start;
struct cfset_request *preq;
void __user *umask;
unsigned int len;
int ret = 0;
size_t need;
if (file->private_data)
return -EBUSY;
ustart = (struct s390_ctrset_start __user *)arg;
if (copy_from_user(&start, ustart, sizeof(start)))
return -EFAULT;
if (start.version != S390_HWCTR_START_VERSION)
return -EINVAL;
if (start.counter_sets & ~(cpumf_ctr_ctl[CPUMF_CTR_SET_BASIC] |
cpumf_ctr_ctl[CPUMF_CTR_SET_USER] |
cpumf_ctr_ctl[CPUMF_CTR_SET_CRYPTO] |
cpumf_ctr_ctl[CPUMF_CTR_SET_EXT] |
cpumf_ctr_ctl[CPUMF_CTR_SET_MT_DIAG]))
return -EINVAL;
if (!start.counter_sets)
return -EINVAL;
preq = kzalloc(sizeof(*preq), GFP_KERNEL);
if (!preq)
return -ENOMEM;
cpumask_clear(&preq->mask);
len = min_t(u64, start.cpumask_len, cpumask_size());
umask = (void __user *)start.cpumask;
if (copy_from_user(&preq->mask, umask, len)) {
kfree(preq);
return -EFAULT;
}
if (cpumask_empty(&preq->mask)) {
kfree(preq);
return -EINVAL;
}
need = cfset_needspace(start.counter_sets);
if (put_user(need, &ustart->data_bytes)) {
kfree(preq);
return -EFAULT;
}
preq->ctrset = start.counter_sets;
ret = cfset_all_start(preq);
if (!ret) {
cfset_session_add(preq);
file->private_data = preq;
} else {
kfree(preq);
}
return ret;
}
static long cfset_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret;
cpus_read_lock();
mutex_lock(&cfset_ctrset_mutex);
switch (cmd) {
case S390_HWCTR_START:
ret = cfset_ioctl_start(arg, file);
break;
case S390_HWCTR_STOP:
ret = cfset_ioctl_stop(file);
break;
case S390_HWCTR_READ:
ret = cfset_ioctl_read(arg, file->private_data);
break;
default:
ret = -ENOTTY;
break;
}
mutex_unlock(&cfset_ctrset_mutex);
cpus_read_unlock();
return ret;
}
static const struct file_operations cfset_fops = {
.owner = THIS_MODULE,
.open = cfset_open,
.release = cfset_release,
.unlocked_ioctl = cfset_ioctl,
};
static struct miscdevice cfset_dev = {
.name = S390_HWCTR_DEVICE,
.minor = MISC_DYNAMIC_MINOR,
.fops = &cfset_fops,
.mode = 0666,
};
static int cfset_online_cpu(unsigned int cpu)
{
struct cfset_call_on_cpu_parm p;
struct cfset_request *rp;
if (!list_empty(&cfset_session.head)) {
list_for_each_entry(rp, &cfset_session.head, node) {
p.sets = rp->ctrset;
cfset_ioctl_on(&p);
cpumask_set_cpu(cpu, &rp->mask);
}
}
return 0;
}
static int cfset_offline_cpu(unsigned int cpu)
{
struct cfset_call_on_cpu_parm p;
struct cfset_request *rp;
if (!list_empty(&cfset_session.head)) {
list_for_each_entry(rp, &cfset_session.head, node) {
p.sets = rp->ctrset;
cfset_ioctl_off(&p);
cpumask_clear_cpu(cpu, &rp->mask);
}
}
return 0;
}
static void cfdiag_read(struct perf_event *event)
{
}
static int get_authctrsets(void)
{
unsigned long auth = 0;
enum cpumf_ctr_set i;
for (i = CPUMF_CTR_SET_BASIC; i < CPUMF_CTR_SET_MAX; ++i) {
if (cpumf_ctr_info.auth_ctl & cpumf_ctr_ctl[i])
auth |= cpumf_ctr_ctl[i];
}
return auth;
}
static int cfdiag_event_init2(struct perf_event *event)
{
struct perf_event_attr *attr = &event->attr;
int err = 0;
event->hw.config = attr->config;
event->hw.sample_period = attr->sample_period;
local64_set(&event->hw.period_left, event->hw.sample_period);
local64_set(&event->count, 0);
event->hw.last_period = event->hw.sample_period;
event->hw.config_base = get_authctrsets();
if (!event->hw.config_base)
err = -EINVAL;
return err;
}
static int cfdiag_event_init(struct perf_event *event)
{
struct perf_event_attr *attr = &event->attr;
int err = -ENOENT;
if (event->attr.config != PERF_EVENT_CPUM_CF_DIAG ||
event->attr.type != event->pmu->type)
goto out;
if (attr->exclude_kernel || attr->exclude_user || attr->exclude_hv ||
!(attr->sample_type & (PERF_SAMPLE_CPU | PERF_SAMPLE_RAW))) {
err = -EOPNOTSUPP;
goto out;
}
if (cpum_cf_alloc(event->cpu))
return -ENOMEM;
event->destroy = hw_perf_event_destroy;
err = cfdiag_event_init2(event);
out:
return err;
}
CPUMF_EVENT_ATTR(CF_DIAG, CF_DIAG, PERF_EVENT_CPUM_CF_DIAG);
static struct attribute *cfdiag_events_attr[] = {
CPUMF_EVENT_PTR(CF_DIAG, CF_DIAG),
NULL,
};
PMU_FORMAT_ATTR(event, "config:0-63");
static struct attribute *cfdiag_format_attr[] = {
&format_attr_event.attr,
NULL,
};
static struct attribute_group cfdiag_events_group = {
.name = "events",
.attrs = cfdiag_events_attr,
};
static struct attribute_group cfdiag_format_group = {
.name = "format",
.attrs = cfdiag_format_attr,
};
static const struct attribute_group *cfdiag_attr_groups[] = {
&cfdiag_events_group,
&cfdiag_format_group,
NULL,
};
static struct pmu cf_diag = {
.task_ctx_nr = perf_sw_context,
.event_init = cfdiag_event_init,
.pmu_enable = cpumf_pmu_enable,
.pmu_disable = cpumf_pmu_disable,
.add = cpumf_pmu_add,
.del = cpumf_pmu_del,
.start = cpumf_pmu_start,
.stop = cpumf_pmu_stop,
.read = cfdiag_read,
.attr_groups = cfdiag_attr_groups
};
static size_t cfdiag_maxsize(struct cpumf_ctr_info *info)
{
size_t max_size = sizeof(struct cf_trailer_entry);
enum cpumf_ctr_set i;
for (i = CPUMF_CTR_SET_BASIC; i < CPUMF_CTR_SET_MAX; ++i) {
size_t size = cpum_cf_read_setsize(i);
if (size)
max_size += size * sizeof(u64) +
sizeof(struct cf_ctrset_entry);
}
return max_size;
}
static void cfdiag_get_cpu_speed(void)
{
unsigned long mhz;
if (cpum_sf_avail()) {
struct hws_qsi_info_block si;
memset(&si, 0, sizeof(si));
if (!qsi(&si)) {
cfdiag_cpu_speed = si.cpu_speed;
return;
}
}
mhz = __ecag(ECAG_CPU_ATTRIBUTE, 0);
if (mhz != -1UL)
cfdiag_cpu_speed = mhz & 0xffffffff;
}
static int cfset_init(void)
{
size_t need;
int rc;
cfdiag_get_cpu_speed();
need = cfdiag_maxsize(&cpumf_ctr_info);
if (need > sizeof(((struct cpu_cf_events *)0)->start)) {
pr_err("Insufficient memory for PMU(cpum_cf_diag) need=%zu\n",
need);
return -ENOMEM;
}
rc = misc_register(&cfset_dev);
if (rc) {
pr_err("Registration of /dev/%s failed rc=%i\n",
cfset_dev.name, rc);
goto out;
}
rc = perf_pmu_register(&cf_diag, "cpum_cf_diag", -1);
if (rc) {
misc_deregister(&cfset_dev);
pr_err("Registration of PMU(cpum_cf_diag) failed with rc=%i\n",
rc);
}
out:
return rc;
}
device_initcall(cpumf_pmu_init);