Skip to content

Commit 4905ec2

Browse files
atishp04palmer-dabbelt
authored andcommitted
RISC-V: Add sscofpmf extension support
The sscofpmf extension allows counter overflow and filtering for programmable counters. Enable the perf driver to handle the overflow interrupt. The overflow interrupt is a hart local interrupt. Thus, per cpu overflow interrupts are setup as a child under the root INTC irq domain. Signed-off-by: Atish Patra <[email protected]> Signed-off-by: Atish Patra <[email protected]> Signed-off-by: Palmer Dabbelt <[email protected]>
1 parent e999143 commit 4905ec2

File tree

6 files changed

+230
-6
lines changed

6 files changed

+230
-6
lines changed

arch/riscv/include/asm/csr.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
#define IRQ_S_EXT 9
6666
#define IRQ_VS_EXT 10
6767
#define IRQ_M_EXT 11
68+
#define IRQ_PMU_OVF 13
6869

6970
/* Exception causes */
7071
#define EXC_INST_MISALIGNED 0
@@ -212,6 +213,8 @@
212213
#define CSR_HPMCOUNTER30H 0xc9e
213214
#define CSR_HPMCOUNTER31H 0xc9f
214215

216+
#define CSR_SSCOUNTOVF 0xda0
217+
215218
#define CSR_SSTATUS 0x100
216219
#define CSR_SIE 0x104
217220
#define CSR_STVEC 0x105
@@ -298,7 +301,10 @@
298301
# define RV_IRQ_SOFT IRQ_S_SOFT
299302
# define RV_IRQ_TIMER IRQ_S_TIMER
300303
# define RV_IRQ_EXT IRQ_S_EXT
301-
#endif /* CONFIG_RISCV_M_MODE */
304+
# define RV_IRQ_PMU IRQ_PMU_OVF
305+
# define SIP_LCOFIP (_AC(0x1, UL) << IRQ_PMU_OVF)
306+
307+
#endif /* !CONFIG_RISCV_M_MODE */
302308

303309
/* IE/IP (Supervisor/Machine Interrupt Enable/Pending) flags */
304310
#define IE_SIE (_AC(0x1, UL) << RV_IRQ_SOFT)

arch/riscv/include/asm/hwcap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ extern unsigned long elf_hwcap;
5151
* available logical extension id.
5252
*/
5353
enum riscv_isa_ext_id {
54+
RISCV_ISA_EXT_SSCOFPMF = RISCV_ISA_EXT_BASE,
5455
RISCV_ISA_EXT_ID_MAX = RISCV_ISA_EXT_MAX,
5556
};
5657

arch/riscv/kernel/cpu.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ int riscv_of_parent_hartid(struct device_node *node)
8787
* extensions by an underscore.
8888
*/
8989
static struct riscv_isa_ext_data isa_ext_arr[] = {
90+
__RISCV_ISA_EXT_DATA(sscofpmf, RISCV_ISA_EXT_SSCOFPMF),
9091
__RISCV_ISA_EXT_DATA("", RISCV_ISA_EXT_MAX),
9192
};
9293

arch/riscv/kernel/cpufeature.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ void __init riscv_fill_hwcap(void)
190190
if (!ext_long) {
191191
this_hwcap |= isa2hwcap[(unsigned char)(*ext)];
192192
set_bit(*ext - 'a', this_isa);
193+
} else {
194+
SET_ISA_EXT_MAP("sscofpmf", RISCV_ISA_EXT_SSCOFPMF);
193195
}
194196
#undef SET_ISA_EXT_MAP
195197
}

drivers/perf/riscv_pmu_sbi.c

Lines changed: 217 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@
1313
#include <linux/mod_devicetable.h>
1414
#include <linux/perf/riscv_pmu.h>
1515
#include <linux/platform_device.h>
16+
#include <linux/irq.h>
17+
#include <linux/irqdomain.h>
18+
#include <linux/of_irq.h>
19+
#include <linux/of.h>
1620

1721
#include <asm/sbi.h>
22+
#include <asm/hwcap.h>
1823

1924
union sbi_pmu_ctr_info {
2025
unsigned long value;
@@ -35,6 +40,7 @@ union sbi_pmu_ctr_info {
3540
* per_cpu in case of harts with different pmu counters
3641
*/
3742
static union sbi_pmu_ctr_info *pmu_ctr_list;
43+
static unsigned int riscv_pmu_irq;
3844

3945
struct sbi_pmu_event_data {
4046
union {
@@ -469,33 +475,229 @@ static int pmu_sbi_get_ctrinfo(int nctr)
469475
return 0;
470476
}
471477

478+
static inline void pmu_sbi_stop_all(struct riscv_pmu *pmu)
479+
{
480+
/**
481+
* No need to check the error because we are disabling all the counters
482+
* which may include counters that are not enabled yet.
483+
*/
484+
sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP,
485+
0, GENMASK_ULL(pmu->num_counters - 1, 0), 0, 0, 0, 0);
486+
}
487+
488+
static inline void pmu_sbi_stop_hw_ctrs(struct riscv_pmu *pmu)
489+
{
490+
struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
491+
492+
/* No need to check the error here as we can't do anything about the error */
493+
sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, 0,
494+
cpu_hw_evt->used_hw_ctrs[0], 0, 0, 0, 0);
495+
}
496+
497+
/**
498+
* This function starts all the used counters in two step approach.
499+
* Any counter that did not overflow can be start in a single step
500+
* while the overflowed counters need to be started with updated initialization
501+
* value.
502+
*/
503+
static inline void pmu_sbi_start_overflow_mask(struct riscv_pmu *pmu,
504+
unsigned long ctr_ovf_mask)
505+
{
506+
int idx = 0;
507+
struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
508+
struct perf_event *event;
509+
unsigned long flag = SBI_PMU_START_FLAG_SET_INIT_VALUE;
510+
unsigned long ctr_start_mask = 0;
511+
uint64_t max_period;
512+
struct hw_perf_event *hwc;
513+
u64 init_val = 0;
514+
515+
ctr_start_mask = cpu_hw_evt->used_hw_ctrs[0] & ~ctr_ovf_mask;
516+
517+
/* Start all the counters that did not overflow in a single shot */
518+
sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, 0, ctr_start_mask,
519+
0, 0, 0, 0);
520+
521+
/* Reinitialize and start all the counter that overflowed */
522+
while (ctr_ovf_mask) {
523+
if (ctr_ovf_mask & 0x01) {
524+
event = cpu_hw_evt->events[idx];
525+
hwc = &event->hw;
526+
max_period = riscv_pmu_ctr_get_width_mask(event);
527+
init_val = local64_read(&hwc->prev_count) & max_period;
528+
sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, idx, 1,
529+
flag, init_val, 0, 0);
530+
}
531+
ctr_ovf_mask = ctr_ovf_mask >> 1;
532+
idx++;
533+
}
534+
}
535+
536+
static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev)
537+
{
538+
struct perf_sample_data data;
539+
struct pt_regs *regs;
540+
struct hw_perf_event *hw_evt;
541+
union sbi_pmu_ctr_info *info;
542+
int lidx, hidx, fidx;
543+
struct riscv_pmu *pmu;
544+
struct perf_event *event;
545+
unsigned long overflow;
546+
unsigned long overflowed_ctrs = 0;
547+
struct cpu_hw_events *cpu_hw_evt = dev;
548+
549+
if (WARN_ON_ONCE(!cpu_hw_evt))
550+
return IRQ_NONE;
551+
552+
/* Firmware counter don't support overflow yet */
553+
fidx = find_first_bit(cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS);
554+
event = cpu_hw_evt->events[fidx];
555+
if (!event) {
556+
csr_clear(CSR_SIP, SIP_LCOFIP);
557+
return IRQ_NONE;
558+
}
559+
560+
pmu = to_riscv_pmu(event->pmu);
561+
pmu_sbi_stop_hw_ctrs(pmu);
562+
563+
/* Overflow status register should only be read after counter are stopped */
564+
overflow = csr_read(CSR_SSCOUNTOVF);
565+
566+
/**
567+
* Overflow interrupt pending bit should only be cleared after stopping
568+
* all the counters to avoid any race condition.
569+
*/
570+
csr_clear(CSR_SIP, SIP_LCOFIP);
571+
572+
/* No overflow bit is set */
573+
if (!overflow)
574+
return IRQ_NONE;
575+
576+
regs = get_irq_regs();
577+
578+
for_each_set_bit(lidx, cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS) {
579+
struct perf_event *event = cpu_hw_evt->events[lidx];
580+
581+
/* Skip if invalid event or user did not request a sampling */
582+
if (!event || !is_sampling_event(event))
583+
continue;
584+
585+
info = &pmu_ctr_list[lidx];
586+
/* Do a sanity check */
587+
if (!info || info->type != SBI_PMU_CTR_TYPE_HW)
588+
continue;
589+
590+
/* compute hardware counter index */
591+
hidx = info->csr - CSR_CYCLE;
592+
/* check if the corresponding bit is set in sscountovf */
593+
if (!(overflow & (1 << hidx)))
594+
continue;
595+
596+
/*
597+
* Keep a track of overflowed counters so that they can be started
598+
* with updated initial value.
599+
*/
600+
overflowed_ctrs |= 1 << lidx;
601+
hw_evt = &event->hw;
602+
riscv_pmu_event_update(event);
603+
perf_sample_data_init(&data, 0, hw_evt->last_period);
604+
if (riscv_pmu_event_set_period(event)) {
605+
/*
606+
* Unlike other ISAs, RISC-V don't have to disable interrupts
607+
* to avoid throttling here. As per the specification, the
608+
* interrupt remains disabled until the OF bit is set.
609+
* Interrupts are enabled again only during the start.
610+
* TODO: We will need to stop the guest counters once
611+
* virtualization support is added.
612+
*/
613+
perf_event_overflow(event, &data, regs);
614+
}
615+
}
616+
pmu_sbi_start_overflow_mask(pmu, overflowed_ctrs);
617+
618+
return IRQ_HANDLED;
619+
}
620+
472621
static int pmu_sbi_starting_cpu(unsigned int cpu, struct hlist_node *node)
473622
{
474623
struct riscv_pmu *pmu = hlist_entry_safe(node, struct riscv_pmu, node);
624+
struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
475625

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

479629
/* Stop all the counters so that they can be enabled from perf */
480-
sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP,
481-
0, GENMASK_ULL(pmu->num_counters - 1, 0), 0, 0, 0, 0);
630+
pmu_sbi_stop_all(pmu);
631+
632+
if (riscv_isa_extension_available(NULL, SSCOFPMF)) {
633+
cpu_hw_evt->irq = riscv_pmu_irq;
634+
csr_clear(CSR_IP, BIT(RV_IRQ_PMU));
635+
csr_set(CSR_IE, BIT(RV_IRQ_PMU));
636+
enable_percpu_irq(riscv_pmu_irq, IRQ_TYPE_NONE);
637+
}
482638

483639
return 0;
484640
}
485641

486642
static int pmu_sbi_dying_cpu(unsigned int cpu, struct hlist_node *node)
487643
{
644+
if (riscv_isa_extension_available(NULL, SSCOFPMF)) {
645+
disable_percpu_irq(riscv_pmu_irq);
646+
csr_clear(CSR_IE, BIT(RV_IRQ_PMU));
647+
}
648+
488649
/* Disable all counters access for user mode now */
489650
csr_write(CSR_SCOUNTEREN, 0x0);
490651

491652
return 0;
492653
}
493654

655+
static int pmu_sbi_setup_irqs(struct riscv_pmu *pmu, struct platform_device *pdev)
656+
{
657+
int ret;
658+
struct cpu_hw_events __percpu *hw_events = pmu->hw_events;
659+
struct device_node *cpu, *child;
660+
struct irq_domain *domain = NULL;
661+
662+
if (!riscv_isa_extension_available(NULL, SSCOFPMF))
663+
return -EOPNOTSUPP;
664+
665+
for_each_of_cpu_node(cpu) {
666+
child = of_get_compatible_child(cpu, "riscv,cpu-intc");
667+
if (!child) {
668+
pr_err("Failed to find INTC node\n");
669+
return -ENODEV;
670+
}
671+
domain = irq_find_host(child);
672+
of_node_put(child);
673+
if (domain)
674+
break;
675+
}
676+
if (!domain) {
677+
pr_err("Failed to find INTC IRQ root domain\n");
678+
return -ENODEV;
679+
}
680+
681+
riscv_pmu_irq = irq_create_mapping(domain, RV_IRQ_PMU);
682+
if (!riscv_pmu_irq) {
683+
pr_err("Failed to map PMU interrupt for node\n");
684+
return -ENODEV;
685+
}
686+
687+
ret = request_percpu_irq(riscv_pmu_irq, pmu_sbi_ovf_handler, "riscv-pmu", hw_events);
688+
if (ret) {
689+
pr_err("registering percpu irq failed [%d]\n", ret);
690+
return ret;
691+
}
692+
693+
return 0;
694+
}
695+
494696
static int pmu_sbi_device_probe(struct platform_device *pdev)
495697
{
496698
struct riscv_pmu *pmu = NULL;
497699
int num_counters;
498-
int ret;
700+
int ret = -ENODEV;
499701

500702
pr_info("SBI PMU extension is available\n");
501703
pmu = riscv_pmu_alloc();
@@ -505,13 +707,19 @@ static int pmu_sbi_device_probe(struct platform_device *pdev)
505707
num_counters = pmu_sbi_find_num_ctrs();
506708
if (num_counters < 0) {
507709
pr_err("SBI PMU extension doesn't provide any counters\n");
508-
return -ENODEV;
710+
goto out_free;
509711
}
510712

511713
/* cache all the information about counters now */
512714
if (pmu_sbi_get_ctrinfo(num_counters))
513-
return -ENODEV;
715+
goto out_free;
514716

717+
ret = pmu_sbi_setup_irqs(pmu, pdev);
718+
if (ret < 0) {
719+
pr_info("Perf sampling/filtering is not supported as sscof extension is not available\n");
720+
pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
721+
pmu->pmu.capabilities |= PERF_PMU_CAP_NO_EXCLUDE;
722+
}
515723
pmu->num_counters = num_counters;
516724
pmu->ctr_start = pmu_sbi_ctr_start;
517725
pmu->ctr_stop = pmu_sbi_ctr_stop;
@@ -532,6 +740,10 @@ static int pmu_sbi_device_probe(struct platform_device *pdev)
532740
}
533741

534742
return 0;
743+
744+
out_free:
745+
kfree(pmu);
746+
return ret;
535747
}
536748

537749
static struct platform_driver pmu_sbi_driver = {

include/linux/perf/riscv_pmu.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
struct cpu_hw_events {
3030
/* currently enabled events */
3131
int n_events;
32+
/* Counter overflow interrupt */
33+
int irq;
3234
/* currently enabled events */
3335
struct perf_event *events[RISCV_MAX_COUNTERS];
3436
/* currently enabled hardware counters */

0 commit comments

Comments
 (0)