linux-thead/drivers/perf/thead_c900_pmu_v1.c

801 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd. */
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/perf_event.h>
#include <linux/platform_device.h>
#include <linux/smp.h>
#include <asm/sbi.h>
#define RISCV_MAX_COUNTERS 32
#define RISCV_OP_UNSUPP (-EOPNOTSUPP)
#define RISCV_PMU_CYCLE 0
#define RISCV_PMU_TIME 1
#define RISCV_PMU_INSTRET 2
#define RISCV_PMU_L1ICAC 3 /* ICache Access */
#define RISCV_PMU_L1ICMC 4 /* ICache Miss */
#define RISCV_PMU_IUTLBMC 5 /* I-UTLB Miss */
#define RISCV_PMU_DUTLBMC 6 /* D-UTLB Miss */
#define RISCV_PMU_JTLBMC 7 /* JTLB Miss Counter */
#define RISCV_PMU_CBMC 8 /* Cond-br-mispredict */
#define RISCV_PMU_CBIC 9 /* Cond-br-instruction */
#define RISCV_PMU_IBMC 10 /* Indirect Branch Mispredict */
#define RISCV_PMU_IBIC 11 /* Indirect Branch Instruction */
#define RISCV_PMU_LSUSFC 12 /* LSU Spec Fail */
#define RISCV_PMU_STC 13 /* Store Instruction */
#define RISCV_PMU_L1DCRAC 14 /* L1 DCache Read Access */
#define RISCV_PMU_L1DCRMC 15 /* L1 DCache Read Miss */
#define RISCV_PMU_L1DCWAC 16 /* L1 DCache Write Access */
#define RISCV_PMU_L1DCWMC 17 /* L1 DCache Write Miss */
#define RISCV_PMU_L2CRAC 18 /* L2 Cache Read Access */
#define RISCV_PMU_L2CRMC 19 /* L2 Cache Read Miss */
#define RISCV_PMU_L2CWAC 20 /* L2 Cache Write Access */
#define RISCV_PMU_L2CWMC 21 /* L2 Cache Write Miss */
#define RISCV_PMU_RFLFC 22 /* RF Launch Fail */
#define RISCV_PMU_RFRLFC 23 /* RF Reg Launch Fail */
#define RISCV_PMU_RFIC 24 /* RF Instruction */
#define RISCV_PMU_LSUC4SC 25 /* LSU Cross 4K Stall */
#define RISCV_PMU_LSUOSC 26 /* LSU Other Stall */
#define RISCV_PMU_LSUSQDC 27 /* LSU SQ Discard */
#define RISCV_PMU_LSUSQDDC 28 /* LSU SQ Data Discard */
#define SCOUNTERINTEN 0x5c4
#define SCOUNTEROF 0x5c5
#define SCOUNTERBASE 0x5e0
#define WRITE_COUNTER(idx, value) \
csr_write(SCOUNTERBASE + idx, value)
/* The events for a given PMU register set. */
struct pmu_hw_events {
/*
* The events that are active on the PMU for the given index.
*/
struct perf_event *events[RISCV_MAX_COUNTERS];
/*
* A 1 bit for an index indicates that the counter is being used for
* an event. A 0 means that the counter can be used.
*/
unsigned long used_mask[BITS_TO_LONGS(RISCV_MAX_COUNTERS)];
};
static struct riscv_pmu_t {
struct pmu pmu;
struct pmu_hw_events __percpu *hw_events;
struct platform_device *plat_device;
u64 max_period;
} riscv_pmu;
static int riscv_pmu_irq;
/*
* Hardware & cache maps and their methods
*/
static const int riscv_hw_event_map[] = {
[PERF_COUNT_HW_CPU_CYCLES] = RISCV_PMU_CYCLE,
[PERF_COUNT_HW_INSTRUCTIONS] = RISCV_PMU_INSTRET,
[PERF_COUNT_HW_CACHE_REFERENCES] = RISCV_PMU_L1ICAC,
[PERF_COUNT_HW_CACHE_MISSES] = RISCV_PMU_L1ICMC,
[PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = RISCV_PMU_CBIC,
[PERF_COUNT_HW_BRANCH_MISSES] = RISCV_PMU_CBMC,
[PERF_COUNT_HW_BUS_CYCLES] = RISCV_PMU_IBMC,
[PERF_COUNT_HW_STALLED_CYCLES_FRONTEND] = RISCV_PMU_IBIC,
[PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = RISCV_PMU_LSUSFC,
[PERF_COUNT_HW_REF_CPU_CYCLES] = RISCV_PMU_STC,
};
#define C(x) PERF_COUNT_HW_CACHE_##x
static const int riscv_cache_event_map[PERF_COUNT_HW_CACHE_MAX]
[PERF_COUNT_HW_CACHE_OP_MAX]
[PERF_COUNT_HW_CACHE_RESULT_MAX] = {
[C(L1D)] = {
[C(OP_READ)] = {
[C(RESULT_ACCESS)] = RISCV_PMU_L1DCRAC,
[C(RESULT_MISS)] = RISCV_PMU_L1DCRMC,
},
[C(OP_WRITE)] = {
[C(RESULT_ACCESS)] = RISCV_PMU_L1DCWAC,
[C(RESULT_MISS)] = RISCV_PMU_L1DCWMC,
},
[C(OP_PREFETCH)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
},
[C(L1I)] = {
[C(OP_READ)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
[C(OP_WRITE)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
[C(OP_PREFETCH)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
},
[C(LL)] = {
[C(OP_READ)] = {
[C(RESULT_ACCESS)] = RISCV_PMU_L2CRAC,
[C(RESULT_MISS)] = RISCV_PMU_L2CRMC,
},
[C(OP_WRITE)] = {
[C(RESULT_ACCESS)] = RISCV_PMU_L2CWAC,
[C(RESULT_MISS)] = RISCV_PMU_L2CWMC,
},
[C(OP_PREFETCH)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
},
[C(DTLB)] = {
[C(OP_READ)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
[C(OP_WRITE)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
[C(OP_PREFETCH)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
},
[C(ITLB)] = {
[C(OP_READ)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
[C(OP_WRITE)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
[C(OP_PREFETCH)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
},
[C(BPU)] = {
[C(OP_READ)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
[C(OP_WRITE)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
[C(OP_PREFETCH)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
},
[C(NODE)] = {
[C(OP_READ)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
[C(OP_WRITE)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
[C(OP_PREFETCH)] = {
[C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
[C(RESULT_MISS)] = RISCV_OP_UNSUPP,
},
},
};
/*
* Low-level functions: reading/writing counters
*/
static inline u64 read_counter(int idx)
{
u64 val = 0;
switch (idx) {
case RISCV_PMU_CYCLE:
val = csr_read(cycle);
break;
case RISCV_PMU_INSTRET:
val = csr_read(instret);
break;
case RISCV_PMU_L1ICAC:
val = csr_read(hpmcounter3);
break;
case RISCV_PMU_L1ICMC:
val = csr_read(hpmcounter4);
break;
case RISCV_PMU_IUTLBMC:
val = csr_read(hpmcounter5);
break;
case RISCV_PMU_DUTLBMC:
val = csr_read(hpmcounter6);
break;
case RISCV_PMU_JTLBMC:
val = csr_read(hpmcounter7);
break;
case RISCV_PMU_CBMC:
val = csr_read(hpmcounter8);
break;
case RISCV_PMU_CBIC:
val = csr_read(hpmcounter9);
break;
case RISCV_PMU_IBMC:
val = csr_read(hpmcounter10);
break;
case RISCV_PMU_IBIC:
val = csr_read(hpmcounter11);
break;
case RISCV_PMU_LSUSFC:
val = csr_read(hpmcounter12);
break;
case RISCV_PMU_STC:
val = csr_read(hpmcounter13);
break;
case RISCV_PMU_L1DCRAC:
val = csr_read(hpmcounter14);
break;
case RISCV_PMU_L1DCRMC:
val = csr_read(hpmcounter15);
break;
case RISCV_PMU_L1DCWAC:
val = csr_read(hpmcounter16);
break;
case RISCV_PMU_L1DCWMC:
val = csr_read(hpmcounter17);
break;
case RISCV_PMU_L2CRAC:
val = csr_read(hpmcounter18);
break;
case RISCV_PMU_L2CRMC:
val = csr_read(hpmcounter19);
break;
case RISCV_PMU_L2CWAC:
val = csr_read(hpmcounter20);
break;
case RISCV_PMU_L2CWMC:
val = csr_read(hpmcounter21);
break;
case RISCV_PMU_RFLFC:
val = csr_read(hpmcounter22);
break;
case RISCV_PMU_RFRLFC:
val = csr_read(hpmcounter23);
break;
case RISCV_PMU_RFIC:
val = csr_read(hpmcounter24);
break;
case RISCV_PMU_LSUC4SC:
val = csr_read(hpmcounter25);
break;
case RISCV_PMU_LSUOSC:
val = csr_read(hpmcounter26);
break;
case RISCV_PMU_LSUSQDC:
val = csr_read(hpmcounter27);
break;
case RISCV_PMU_LSUSQDDC:
val = csr_read(hpmcounter28);
break;
default:
WARN_ON_ONCE(idx < 0 || idx > RISCV_MAX_COUNTERS);
return -EINVAL;
}
return val;
}
static inline void write_counter(int idx, u64 value)
{
switch (idx) {
case RISCV_PMU_CYCLE:
WRITE_COUNTER(RISCV_PMU_CYCLE, value);
break;
case RISCV_PMU_INSTRET:
WRITE_COUNTER(RISCV_PMU_INSTRET, value);
break;
case RISCV_PMU_L1ICAC:
WRITE_COUNTER(RISCV_PMU_L1ICAC, value);
break;
case RISCV_PMU_L1ICMC:
WRITE_COUNTER(RISCV_PMU_L1ICMC, value);
break;
case RISCV_PMU_IUTLBMC:
WRITE_COUNTER(RISCV_PMU_IUTLBMC, value);
break;
case RISCV_PMU_DUTLBMC:
WRITE_COUNTER(RISCV_PMU_DUTLBMC, value);
break;
case RISCV_PMU_JTLBMC:
WRITE_COUNTER(RISCV_PMU_JTLBMC, value);
break;
case RISCV_PMU_CBMC:
WRITE_COUNTER(RISCV_PMU_CBMC, value);
break;
case RISCV_PMU_CBIC:
WRITE_COUNTER(RISCV_PMU_CBIC, value);
break;
case RISCV_PMU_IBMC:
WRITE_COUNTER(RISCV_PMU_IBMC, value);
break;
case RISCV_PMU_IBIC:
WRITE_COUNTER(RISCV_PMU_IBIC, value);
break;
case RISCV_PMU_LSUSFC:
WRITE_COUNTER(RISCV_PMU_LSUSFC, value);
break;
case RISCV_PMU_STC:
WRITE_COUNTER(RISCV_PMU_STC, value);
break;
case RISCV_PMU_L1DCRAC:
WRITE_COUNTER(RISCV_PMU_L1DCRAC, value);
break;
case RISCV_PMU_L1DCRMC:
WRITE_COUNTER(RISCV_PMU_L1DCRMC, value);
break;
case RISCV_PMU_L1DCWAC:
WRITE_COUNTER(RISCV_PMU_L1DCWAC, value);
break;
case RISCV_PMU_L1DCWMC:
WRITE_COUNTER(RISCV_PMU_L1DCWMC, value);
break;
case RISCV_PMU_L2CRAC:
WRITE_COUNTER(RISCV_PMU_L2CRAC, value);
break;
case RISCV_PMU_L2CRMC:
WRITE_COUNTER(RISCV_PMU_L2CRMC, value);
break;
case RISCV_PMU_L2CWAC:
WRITE_COUNTER(RISCV_PMU_L2CWAC, value);
break;
case RISCV_PMU_L2CWMC:
WRITE_COUNTER(RISCV_PMU_L2CWMC, value);
break;
case RISCV_PMU_RFLFC:
WRITE_COUNTER(RISCV_PMU_RFLFC, value);
break;
case RISCV_PMU_RFRLFC:
WRITE_COUNTER(RISCV_PMU_RFRLFC, value);
break;
case RISCV_PMU_RFIC:
WRITE_COUNTER(RISCV_PMU_RFIC, value);
break;
case RISCV_PMU_LSUC4SC:
WRITE_COUNTER(RISCV_PMU_LSUC4SC, value);
break;
case RISCV_PMU_LSUOSC:
WRITE_COUNTER(RISCV_PMU_LSUOSC, value);
break;
case RISCV_PMU_LSUSQDC:
WRITE_COUNTER(RISCV_PMU_LSUSQDC, value);
break;
case RISCV_PMU_LSUSQDDC:
WRITE_COUNTER(RISCV_PMU_LSUSQDDC, value);
break;
default:
WARN_ON_ONCE(idx < 0 || idx > RISCV_MAX_COUNTERS);
}
}
static int riscv_pmu_event_is_frequent(int idx)
{
return idx >= RISCV_PMU_CYCLE &&
idx <= RISCV_PMU_L1DCWMC;
}
static int riscv_pmu_event_set_period(struct perf_event *event)
{
struct hw_perf_event *hwc = &event->hw;
s64 left = local64_read(&hwc->period_left);
s64 period = hwc->sample_period;
int ret = 0;
if (period < 4096 && period != 0 &&
riscv_pmu_event_is_frequent(hwc->idx)) {
hwc->sample_period = period = 4096;
}
if (unlikely(left <= -period)) {
left = period;
local64_set(&hwc->period_left, left);
hwc->last_period = period;
ret = 1;
}
if (unlikely(left <= 0)) {
left += period;
local64_set(&hwc->period_left, left);
hwc->last_period = period;
ret = 1;
}
if (left < 0)
left = riscv_pmu.max_period;
/*
* The hw event starts counting from this event offset,
* mark it to be able to extract future "deltas":
*/
local64_set(&hwc->prev_count, (u64)(-left));
csr_write(SCOUNTEROF, csr_read(SCOUNTEROF) & ~BIT(hwc->idx));
write_counter(hwc->idx, (u64)(-left));
perf_event_update_userpage(event);
return ret;
}
static void riscv_perf_event_update(struct perf_event *event,
struct hw_perf_event *hwc)
{
uint64_t prev_raw_count = local64_read(&hwc->prev_count);
/*
* Sign extend count value to 64bit, otherwise delta calculation
* would be incorrect when overflow occurs.
*/
uint64_t new_raw_count = read_counter(hwc->idx);
int64_t delta = new_raw_count - prev_raw_count;
/*
* We aren't afraid of hwc->prev_count changing beneath our feet
* because there's no way for us to re-enter this function anytime.
*/
local64_set(&hwc->prev_count, new_raw_count);
local64_add(delta, &event->count);
local64_sub(delta, &hwc->period_left);
}
static void riscv_pmu_read(struct perf_event *event)
{
riscv_perf_event_update(event, &event->hw);
}
static int riscv_pmu_cache_event(u64 config)
{
unsigned int cache_type, cache_op, cache_result;
cache_type = (config >> 0) & 0xff;
cache_op = (config >> 8) & 0xff;
cache_result = (config >> 16) & 0xff;
if (cache_type >= PERF_COUNT_HW_CACHE_MAX)
return -EINVAL;
if (cache_op >= PERF_COUNT_HW_CACHE_OP_MAX)
return -EINVAL;
if (cache_result >= PERF_COUNT_HW_CACHE_RESULT_MAX)
return -EINVAL;
return riscv_cache_event_map[cache_type][cache_op][cache_result];
}
static int riscv_pmu_event_init(struct perf_event *event)
{
struct hw_perf_event *hwc = &event->hw;
int ret;
switch (event->attr.type) {
case PERF_TYPE_HARDWARE:
if (event->attr.config >= PERF_COUNT_HW_MAX)
return -ENOENT;
ret = riscv_hw_event_map[event->attr.config];
if (ret == RISCV_OP_UNSUPP)
return -ENOENT;
hwc->idx = ret;
break;
case PERF_TYPE_HW_CACHE:
ret = riscv_pmu_cache_event(event->attr.config);
if (ret == RISCV_OP_UNSUPP)
return -ENOENT;
hwc->idx = ret;
break;
case PERF_TYPE_RAW:
if (event->attr.config < 0 || event->attr.config >
RISCV_MAX_COUNTERS)
return -ENOENT;
hwc->idx = event->attr.config;
break;
default:
return -ENOENT;
}
return 0;
}
static void riscv_pmu_enable(struct pmu *pmu)
{
}
/* stops all counters */
static void riscv_pmu_disable(struct pmu *pmu)
{
}
static void riscv_pmu_start(struct perf_event *event, int flags)
{
unsigned long flg;
struct hw_perf_event *hwc = &event->hw;
int idx = hwc->idx;
if (WARN_ON_ONCE(idx == -1))
return;
if (flags & PERF_EF_RELOAD)
WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE));
hwc->state = 0;
riscv_pmu_event_set_period(event);
local_irq_save(flg);
csr_write(SCOUNTERINTEN, BIT(idx) | csr_read(SCOUNTERINTEN));
local_irq_restore(flg);
}
static void riscv_pmu_stop_event(struct perf_event *event)
{
unsigned long flg;
struct hw_perf_event *hwc = &event->hw;
int idx = hwc->idx;
local_irq_save(flg);
csr_write(SCOUNTERINTEN, ~BIT(idx) & csr_read(SCOUNTERINTEN));
local_irq_restore(flg);
}
static void riscv_pmu_stop(struct perf_event *event, int flags)
{
if (!(event->hw.state & PERF_HES_STOPPED)) {
riscv_pmu_stop_event(event);
event->hw.state |= PERF_HES_STOPPED;
}
if ((flags & PERF_EF_UPDATE) &&
!(event->hw.state & PERF_HES_UPTODATE)) {
riscv_perf_event_update(event, &event->hw);
event->hw.state |= PERF_HES_UPTODATE;
}
}
static void riscv_pmu_del(struct perf_event *event, int flags)
{
struct pmu_hw_events *hw_events = this_cpu_ptr(riscv_pmu.hw_events);
struct hw_perf_event *hwc = &event->hw;
riscv_pmu_stop(event, PERF_EF_UPDATE);
hw_events->events[hwc->idx] = NULL;
perf_event_update_userpage(event);
}
/* allocate hardware counter and optionally start counting */
static int riscv_pmu_add(struct perf_event *event, int flags)
{
struct pmu_hw_events *hw_events = this_cpu_ptr(riscv_pmu.hw_events);
struct hw_perf_event *hwc = &event->hw;
hw_events->events[hwc->idx] = event;
hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED;
if (flags & PERF_EF_START)
riscv_pmu_start(event, PERF_EF_RELOAD);
perf_event_update_userpage(event);
return 0;
}
static irqreturn_t riscv_pmu_handle_irq(int irq, void *dev_id)
{
struct perf_sample_data data;
struct pmu_hw_events *cpuc = this_cpu_ptr(riscv_pmu.hw_events);
struct pt_regs *regs;
int idx;
/*
* Did an overflow occur?
*/
if (!csr_read(SCOUNTEROF))
return IRQ_NONE;
/*
* Handle the counter(s) overflow(s)
*/
regs = get_irq_regs();
for (idx = 0; idx < RISCV_MAX_COUNTERS; ++idx) {
struct perf_event *event = cpuc->events[idx];
struct hw_perf_event *hwc;
/* Ignore if we don't have an event. */
if (!event)
continue;
/*
* We have a single interrupt for all counters. Check that
* each counter has overflowed before we process it.
*/
if (!(csr_read(SCOUNTEROF) & BIT(idx)))
continue;
hwc = &event->hw;
riscv_perf_event_update(event, &event->hw);
perf_sample_data_init(&data, 0, hwc->last_period);
riscv_pmu_event_set_period(event);
if (perf_event_overflow(event, &data, regs))
riscv_pmu_stop_event(event);
}
/*
* Handle the pending perf events.
*
* Note: this call *must* be run with interrupts disabled. For
* platforms that can have the PMU interrupts raised as an NMI, this
* will not work.
*/
irq_work_run();
return IRQ_HANDLED;
}
static int riscv_pmu_request_irq(irq_handler_t handler)
{
int err, irqs;
struct platform_device *pmu_device = riscv_pmu.plat_device;
if (!pmu_device)
return -ENODEV;
irqs = min(pmu_device->num_resources, num_possible_cpus());
if (irqs < 1) {
pr_err("no irqs for PMUs defined\n");
return -ENODEV;
}
riscv_pmu_irq = platform_get_irq(pmu_device, 0);
if (riscv_pmu_irq < 0)
return -ENODEV;
err = request_percpu_irq(riscv_pmu_irq, handler, "c9xx-pmu-v1", &riscv_pmu);
if (err) {
pr_err("unable to request IRQ%d for c9xx PMU counters\n",
riscv_pmu_irq);
return err;
}
return 0;
}
static void riscv_pmu_free_irq(void)
{
int irq;
struct platform_device *pmu_device = riscv_pmu.plat_device;
irq = platform_get_irq(pmu_device, 0);
if (irq >= 0)
free_percpu_irq(irq, this_cpu_ptr(riscv_pmu.hw_events));
}
static int init_hw_perf_events(void)
{
riscv_pmu.hw_events = alloc_percpu_gfp(struct pmu_hw_events,
GFP_KERNEL);
if (!riscv_pmu.hw_events) {
pr_info("failed to allocate per-cpu PMU data.\n");
return -ENOMEM;
}
riscv_pmu.pmu = (struct pmu) {
.pmu_enable = riscv_pmu_enable,
.pmu_disable = riscv_pmu_disable,
.event_init = riscv_pmu_event_init,
.add = riscv_pmu_add,
.del = riscv_pmu_del,
.start = riscv_pmu_start,
.stop = riscv_pmu_stop,
.read = riscv_pmu_read,
};
return 0;
}
static int riscv_pmu_starting_cpu(unsigned int cpu)
{
sbi_ecall(0x09000001, 0, 1, 0, 0, 0, 0, 0);
enable_percpu_irq(riscv_pmu_irq, 0);
return 0;
}
static int riscv_pmu_dying_cpu(unsigned int cpu)
{
disable_percpu_irq(riscv_pmu_irq);
return 0;
}
static int riscv_pmu_device_probe(struct platform_device *pdev,
const struct of_device_id *of_table)
{
int ret;
ret = init_hw_perf_events();
if (ret) {
pr_notice("[perf] failed to probe PMU!\n");
return ret;
}
riscv_pmu.max_period = ULONG_MAX;
riscv_pmu.plat_device = pdev;
ret = riscv_pmu_request_irq(riscv_pmu_handle_irq);
if (ret) {
riscv_pmu.pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
pr_notice("[perf] PMU request irq fail!\n");
}
ret = cpuhp_setup_state(CPUHP_AP_PERF_ONLINE + 1, "perf riscv:online",
riscv_pmu_starting_cpu,
riscv_pmu_dying_cpu);
if (ret) {
riscv_pmu_free_irq();
free_percpu(riscv_pmu.hw_events);
return ret;
}
ret = perf_pmu_register(&riscv_pmu.pmu, "thead_xt_pmu", PERF_TYPE_RAW);
if (ret) {
riscv_pmu_free_irq();
free_percpu(riscv_pmu.hw_events);
}
pr_notice("[perf] T-HEAD C900 PMU v1 probed\n");
return ret;
}
const static struct of_device_id riscv_pmu_of_device_ids[] = {
{.compatible = "riscv,thead_xt_pmu"},
{.compatible = "riscv,c910_pmu"},
{},
};
static int riscv_pmu_dev_probe(struct platform_device *pdev)
{
return riscv_pmu_device_probe(pdev, riscv_pmu_of_device_ids);
}
static struct platform_driver riscv_pmu_driver = {
.driver = {
.name = "thead_xt_pmu_v1",
.of_match_table = riscv_pmu_of_device_ids,
},
.probe = riscv_pmu_dev_probe,
};
int __init riscv_pmu_v1_probe(void)
{
return platform_driver_register(&riscv_pmu_driver);
}
device_initcall(riscv_pmu_v1_probe);