Skip to content
Permalink
Browse files
RISC-V: Add interrupt support for perf
The Sscof extension allows counter overflow and filtering for programmable
counters. Enable the perf driver to handle the overflow interrupt.
Even though the perf overflow interrupt is a local one, it is parsed from
DT for simplification. Thus, the DT node with interrupt-extended property
is mandatory for any platform that wants event sampling.

Signed-off-by: Atish Patra <atish.patra@wdc.com>
  • Loading branch information
atishp04 authored and Atish Patra committed Dec 17, 2021
1 parent cc07a68 commit 7b33e2daa65b35b1541d0061a5066efdbac70a4d
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 16 deletions.
@@ -63,6 +63,7 @@
#define IRQ_M_TIMER 7
#define IRQ_S_EXT 9
#define IRQ_M_EXT 11
#define IRQ_PMU_OVF 13

/* Exception causes */
#define EXC_INST_MISALIGNED 0
@@ -151,6 +152,8 @@
#define CSR_HPMCOUNTER30H 0xc9e
#define CSR_HPMCOUNTER31H 0xc9f

#define CSR_SSCOUNTOVF 0xda0

#define CSR_SSTATUS 0x100
#define CSR_SIE 0x104
#define CSR_STVEC 0x105
@@ -212,7 +215,10 @@
# define RV_IRQ_SOFT IRQ_S_SOFT
# define RV_IRQ_TIMER IRQ_S_TIMER
# define RV_IRQ_EXT IRQ_S_EXT
#endif /* CONFIG_RISCV_M_MODE */
# define RV_IRQ_PMU IRQ_PMU_OVF
# define SIP_LCOFIP (_AC(0x1, UL) << IRQ_PMU_OVF)

#endif /* !CONFIG_RISCV_M_MODE */

/* IE/IP (Supervisor/Machine Interrupt Enable/Pending) flags */
#define IE_SIE (_AC(0x1, UL) << RV_IRQ_SOFT)
@@ -11,6 +11,9 @@
#include <linux/mod_devicetable.h>
#include <linux/perf/riscv_pmu.h>
#include <linux/platform_device.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/of_irq.h>

#include <asm/sbi.h>

@@ -33,6 +36,7 @@ union sbi_pmu_ctr_info {
* per_cpu in case of harts with different pmu counters
*/
static union sbi_pmu_ctr_info *pmu_ctr_list;
static unsigned int riscv_pmu_irq;

struct pmu_event_data {
union {
@@ -445,33 +449,242 @@ static int pmu_sbi_get_ctrinfo(int nctr)
return 0;
}

static inline void pmu_sbi_stop_all(struct riscv_pmu *pmu)
{
int idx = 0;
struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
unsigned cmask = 0;
union sbi_pmu_ctr_info *info;

/* We should only stop the used hardware counters */
for_each_set_bit(idx, cpu_hw_evt->used_event_ctrs, RISCV_MAX_COUNTERS) {
info = &pmu_ctr_list[idx];
if (info->type != SBI_PMU_CTR_TYPE_FW)
cmask |= (1 << idx);
}
/* No need to check the error here as we can't do anything about the error */
sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, 0, cmask, 0, 0, 0, 0);
}

/**
* This function starts all the used counters in two step approach.
* Any counter that did not overflow can be start in a single step
* while the overflowed counters need to be started with updated initialization
* value.
*/
static inline void pmu_sbi_start_overflow_mask(struct riscv_pmu *pmu,
unsigned long ctr_ovf_mask)
{
int idx = 0;
struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
struct perf_event *event;
unsigned long flag = SBI_PMU_START_FLAG_SET_INIT_VALUE;
unsigned long ctr_start_mask = 0;
uint64_t max_period;
struct hw_perf_event *hwc;
u64 init_val = 0;

ctr_start_mask = cpu_hw_evt->used_event_ctrs[0] & ~ctr_ovf_mask;

/* Start all the counters that did not overflow in a single shot */
sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, 0, ctr_start_mask,
0, 0, 0, 0);

/* Reinitialize and start all the counter that overflowed */
while(ctr_ovf_mask) {
if (ctr_ovf_mask & 0x01) {
event = cpu_hw_evt->events[idx];
hwc = &event->hw;
max_period = riscv_pmu_ctr_get_width_mask(event);
init_val = local64_read(&hwc->prev_count) & max_period;
sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, idx, 1,
flag, init_val, 0, 0);
}
ctr_ovf_mask = ctr_ovf_mask >> 1;
idx++;
}
}

static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev)
{
struct perf_sample_data data;
struct pt_regs *regs;
struct hw_perf_event *hw_evt;
union sbi_pmu_ctr_info *info;
int lidx, hidx, fidx;
struct riscv_pmu *pmu;
struct perf_event *event;
struct cpu_hw_events *cpu_hw_evt = dev;
unsigned long overflow;
unsigned long overflowed_ctrs = 0;

fidx = find_first_bit(cpu_hw_evt->used_event_ctrs, RISCV_MAX_COUNTERS);
event = cpu_hw_evt->events[fidx];
if (!event) {
csr_clear(CSR_SIP, SIP_LCOFIP);
return IRQ_NONE;
}

pmu = to_riscv_pmu(event->pmu);
pmu_sbi_stop_all(pmu);

/* Overflow status register should only be read after counter are stopped */
overflow = csr_read(CSR_SSCOUNTOVF);

/**
* Overflow interrupt pending bit should only be cleared after stopping
* all the counters to avoid any race condition.
*/
csr_clear(CSR_SIP, SIP_LCOFIP);

/* No overflow bit is set */
if (!overflow)
return IRQ_NONE;

regs = get_irq_regs();

for_each_set_bit(lidx, cpu_hw_evt->used_event_ctrs, RISCV_MAX_COUNTERS) {
struct perf_event *event = cpu_hw_evt->events[lidx];

/* Skip if invalid event or user did not request a sampling */
if (!event || !is_sampling_event(event))
continue;

info = &pmu_ctr_list[lidx];
/* Firmware counter don't support overflow yet */
if (!info || info->type == SBI_PMU_CTR_TYPE_FW)
continue;

/* compute hardware counter index */
hidx = info->csr - CSR_CYCLE;
/* check if the corresponding bit is set in sscountovf */
if (!(overflow & (1 << hidx)))
continue;

/*
* Keep a track of overflowed counters so that they can be started
* with updated initial value.
*/
overflowed_ctrs |= 1 << lidx;
hw_evt = &event->hw;
riscv_pmu_event_update(event);
perf_sample_data_init(&data, 0, hw_evt->last_period);
if (riscv_pmu_event_set_period(event)) {
/*
* Unlike other ISAs, RISC-V don't have to disable interrupts
* to avoid throttling here. As per the specification, the
* interrupt remains disabled until the OF bit is set.
* Interrupts are enabled again only during the start.
* TODO: We will need to stop the guest counters once
* virtualization support is added.
*/
perf_event_overflow(event, &data, regs);
}
}
pmu_sbi_start_overflow_mask(pmu, overflowed_ctrs);

return IRQ_HANDLED;
}

static int pmu_sbi_starting_cpu(unsigned int cpu, struct hlist_node *node)
{
struct riscv_pmu *pmu = hlist_entry_safe(node, struct riscv_pmu, node);
struct cpu_hw_events __percpu *hw_events = pmu->hw_events;

/* Enable the access for TIME csr only from the user mode now */
csr_write(CSR_SCOUNTEREN, 0x2);

/* Stop all the counters so that they can be enabled from perf */
sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP,
0, GENMASK_ULL(pmu->num_counters - 1, 0), 0, 0, 0, 0);
pmu_sbi_stop_all(pmu);

hw_events->irq = riscv_pmu_irq;
csr_clear(CSR_IP, BIT(RV_IRQ_PMU));
csr_set(CSR_IE, BIT(RV_IRQ_PMU));
enable_percpu_irq(riscv_pmu_irq, IRQ_TYPE_NONE);

return 0;
}

static int pmu_sbi_dying_cpu(unsigned int cpu, struct hlist_node *node)
{
disable_percpu_irq(riscv_pmu_irq);
csr_clear(CSR_IE, BIT(RV_IRQ_PMU));

/* Disable all counters access for user mode now */
csr_write(CSR_SCOUNTEREN, 0x0);

return 0;
}

static int pmu_sbi_setup_irqs(struct riscv_pmu *pmu, struct platform_device *pdev)
{
int i = 0, num_irqs, ret;
struct cpu_hw_events __percpu *hw_events = pmu->hw_events;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;

num_irqs = of_irq_count(node);

if (num_irqs <= 0) {
dev_warn(dev, "no irqs for PMU, sampling events not supported\n");
return -EPERM;
}

for (i = 0; i < num_irqs; i++) {
struct of_phandle_args parent;
int cpu, hartid;

if (of_irq_parse_one(node, i, &parent)) {
pr_err("%pOFP: failed to parse parent for irq %d.\n", node, i);
continue;
}

if (parent.args[0] != RV_IRQ_PMU) {
pr_err("%pOFP: invalid irq %d for hwirq %d.\n", node, i, parent.args[0]);
continue;
}

hartid = riscv_of_parent_hartid(parent.np);
if (hartid < 0) {
pr_warn("failed to parse hart ID for irq %d.\n", i);
continue;
}

cpu = riscv_hartid_to_cpuid(hartid);
if (cpu < 0) {
pr_warn("Invalid cpuid for irq %d\n", i);
continue;
}
if (riscv_pmu_irq) {
per_cpu(hw_events->irq, cpu) = riscv_pmu_irq;
per_cpu(hw_events->sscof_ext_present, cpu) = true;
} else if (!riscv_pmu_irq && irq_find_host(parent.np)) {
riscv_pmu_irq = irq_of_parse_and_map(node, i);
pr_info("%s: found PMU irq %d\n", __func__, riscv_pmu_irq);
if (riscv_pmu_irq)
ret = request_percpu_irq(riscv_pmu_irq, pmu_sbi_ovf_handler,
"riscv-pmu", hw_events);
if (ret) {
pr_err("registering percpu irq failed [%d]\n", ret);
return ret;
}
if (per_cpu(hw_events->irq, cpu)) {
pr_warn("PMU irq already set!!");
return -EINVAL;
}
per_cpu(hw_events->irq, cpu) = riscv_pmu_irq;
per_cpu(hw_events->sscof_ext_present, cpu) = true;
}
}

return 0;
}

static int pmu_sbi_device_probe(struct platform_device *pdev)
{
struct riscv_pmu *pmu = NULL;
int num_counters;
int ret;
int ret = -ENODEV;

pr_info("SBI PMU extension is available\n");
/* Notify legacy implementation that SBI pmu is available*/
@@ -483,13 +696,19 @@ static int pmu_sbi_device_probe(struct platform_device *pdev)
num_counters = pmu_sbi_find_num_ctrs();
if (num_counters < 0) {
pr_err("SBI PMU extension doesn't provide any counters\n");
return -ENODEV;
goto out_free;
}

/* cache all the information about counters now */
if (pmu_sbi_get_ctrinfo(num_counters))
return -ENODEV;
goto out_free;

ret = pmu_sbi_setup_irqs(pmu, pdev);
if (ret < 0) {
pr_info("Perf sampling/filtering is not supported as sscof extension is not available\n");
pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
pmu->pmu.capabilities |= PERF_PMU_CAP_NO_EXCLUDE;
}
pmu->num_counters = num_counters;
pmu->ctr_start = pmu_sbi_ctr_start;
pmu->ctr_stop = pmu_sbi_ctr_stop;
@@ -510,19 +729,27 @@ static int pmu_sbi_device_probe(struct platform_device *pdev)
}

return 0;

out_free:
kfree(pmu);
return ret;
}

static const struct of_device_id riscv_pmu_of_device_ids[] = {
{.compatible = "riscv,pmu", .data = NULL},
};

static struct platform_driver pmu_sbi_driver = {
.probe = pmu_sbi_device_probe,
.driver = {
.name = RISCV_PMU_PDEV_NAME,
.of_match_table = riscv_pmu_of_device_ids,
},
};

static int __init pmu_sbi_devinit(void)
{
int ret;
struct platform_device *pdev;

if (((sbi_major_version() == 0) && (sbi_minor_version() < 3)) ||
sbi_probe_extension(SBI_EXT_PMU) <= 0) {
@@ -539,14 +766,6 @@ static int __init pmu_sbi_devinit(void)
}

ret = platform_driver_register(&pmu_sbi_driver);
if (ret)
return ret;

pdev = platform_device_register_simple(RISCV_PMU_PDEV_NAME, -1, NULL, 0);
if (IS_ERR(pdev)) {
platform_driver_unregister(&pmu_sbi_driver);
return PTR_ERR(pdev);
}

return ret;
}
@@ -29,18 +29,20 @@
struct cpu_hw_events {
/* currently enabled events */
int n_events;
/* Counter overflow interrupt */
int irq;
/* currently enabled events */
struct perf_event *events[RISCV_MAX_COUNTERS];
/* currently enabled counters */
DECLARE_BITMAP(used_event_ctrs, RISCV_MAX_COUNTERS);
bool sscof_ext_present;
};

struct riscv_pmu {
struct pmu pmu;
char *name;

irqreturn_t (*handle_irq)(int irq_num, void *dev);
int irq;

int num_counters;
u64 (*ctr_read)(struct perf_event *event);

0 comments on commit 7b33e2d

Please sign in to comment.