Support for the OCX alignment counters. Signed-off-by: Jan Glauber <jglau...@cavium.com> --- arch/arm64/kernel/uncore/Makefile | 3 +- arch/arm64/kernel/uncore/uncore_cavium.c | 3 + arch/arm64/kernel/uncore/uncore_cavium.h | 4 + arch/arm64/kernel/uncore/uncore_cavium_ocx_frc.c | 248 +++++++++++++++++++++++ 4 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 arch/arm64/kernel/uncore/uncore_cavium_ocx_frc.c
diff --git a/arch/arm64/kernel/uncore/Makefile b/arch/arm64/kernel/uncore/Makefile index da39f452..752af39 100644 --- a/arch/arm64/kernel/uncore/Makefile +++ b/arch/arm64/kernel/uncore/Makefile @@ -2,4 +2,5 @@ obj-$(CONFIG_ARCH_THUNDER) += uncore_cavium.o \ uncore_cavium_l2c_tad.o \ uncore_cavium_l2c_cbc.o \ uncore_cavium_lmc.o \ - uncore_cavium_ocx_lne.o + uncore_cavium_ocx_lne.o \ + uncore_cavium_ocx_frc.o diff --git a/arch/arm64/kernel/uncore/uncore_cavium.c b/arch/arm64/kernel/uncore/uncore_cavium.c index f2fbdea..9fed1d6 100644 --- a/arch/arm64/kernel/uncore/uncore_cavium.c +++ b/arch/arm64/kernel/uncore/uncore_cavium.c @@ -30,6 +30,8 @@ struct thunder_uncore *event_to_thunder_uncore(struct perf_event *event) return thunder_uncore_lmc; else if (event->pmu->type == thunder_ocx_lne_pmu.type) return thunder_uncore_ocx_lne; + else if (event->pmu->type == thunder_ocx_frc_pmu.type) + return thunder_uncore_ocx_frc; else return NULL; } @@ -218,6 +220,7 @@ static int __init thunder_uncore_init(void) thunder_uncore_l2c_cbc_setup(); thunder_uncore_lmc_setup(); thunder_uncore_ocx_lne_setup(); + thunder_uncore_ocx_frc_setup(); return 0; } late_initcall(thunder_uncore_init); diff --git a/arch/arm64/kernel/uncore/uncore_cavium.h b/arch/arm64/kernel/uncore/uncore_cavium.h index b9bcb42..07bd4f4 100644 --- a/arch/arm64/kernel/uncore/uncore_cavium.h +++ b/arch/arm64/kernel/uncore/uncore_cavium.h @@ -17,6 +17,7 @@ enum uncore_type { L2C_CBC_TYPE, LMC_TYPE, OCX_LNE_TYPE, + OCX_FRC_TYPE, }; extern int thunder_uncore_version; @@ -66,10 +67,12 @@ extern struct thunder_uncore *thunder_uncore_l2c_tad; extern struct thunder_uncore *thunder_uncore_l2c_cbc; extern struct thunder_uncore *thunder_uncore_lmc; extern struct thunder_uncore *thunder_uncore_ocx_lne; +extern struct thunder_uncore *thunder_uncore_ocx_frc; extern struct pmu thunder_l2c_tad_pmu; extern struct pmu thunder_l2c_cbc_pmu; extern struct pmu thunder_lmc_pmu; extern struct pmu thunder_ocx_lne_pmu; +extern struct pmu thunder_ocx_frc_pmu; /* Prototypes */ struct thunder_uncore *event_to_thunder_uncore(struct perf_event *event); @@ -87,3 +90,4 @@ int thunder_uncore_l2c_tad_setup(void); int thunder_uncore_l2c_cbc_setup(void); int thunder_uncore_lmc_setup(void); int thunder_uncore_ocx_lne_setup(void); +int thunder_uncore_ocx_frc_setup(void); diff --git a/arch/arm64/kernel/uncore/uncore_cavium_ocx_frc.c b/arch/arm64/kernel/uncore/uncore_cavium_ocx_frc.c new file mode 100644 index 0000000..7f62019 --- /dev/null +++ b/arch/arm64/kernel/uncore/uncore_cavium_ocx_frc.c @@ -0,0 +1,248 @@ +/* + * Cavium Thunder uncore PMU support, OCX FRC counters. + * + * Copyright 2016 Cavium Inc. + * Author: Jan Glauber <jan.glau...@cavium.com> + */ + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/cpu.h> +#include <linux/io.h> +#include <linux/perf_event.h> +#include <linux/pci.h> + +#include <asm/cpufeature.h> +#include <asm/cputype.h> + +#include "uncore_cavium.h" + +#ifndef PCI_DEVICE_ID_THUNDER_OCX +#define PCI_DEVICE_ID_THUNDER_OCX 0xa013 +#endif + +#define OCX_FRC_NR_COUNTERS 4 +#define OCX_FRC_NR_UNITS 6 +#define OCX_FRC_UNIT_OFFSET 0x8 +#define OCX_FRC_COUNTER_OFFSET 0xfa00 +#define OCX_FRC_CONTROL_OFFSET 0xff00 +#define OCX_FRC_COUNTER_INC 0x80 +#define OCX_FRC_EVENT_MASK 0x1fffff +#define OCX_FRC_STAT_CONTROL_BIT 37 + +/* OCX FRC event list */ +#define OCX_FRC_EVENT_STAT0 0x0 +#define OCX_FRC_EVENT_STAT1 0x1 +#define OCX_FRC_EVENT_STAT2 0x2 +#define OCX_FRC_EVENT_STAT3 0x3 + +struct thunder_uncore *thunder_uncore_ocx_frc; + +static inline void __iomem *map_offset_ocx_frc(unsigned long addr, + struct thunder_uncore *uncore, int unit) +{ + return (void __iomem *) (addr + + uncore->pdevs[0].map + + unit * OCX_FRC_UNIT_OFFSET); +} + +/* + * Summarize counters across all FRC's. Different from the other uncore + * PMUs because all FRC's are on one PCI device. + */ +static void thunder_uncore_read_ocx_frc(struct perf_event *event) +{ + struct thunder_uncore *uncore = event_to_thunder_uncore(event); + struct hw_perf_event *hwc = &event->hw; + u64 prev, new, sum = 0; + s64 delta; + int i; + + /* + * since we do not enable counter overflow interrupts, + * we do not have to worry about prev_count changing on us + */ + + prev = local64_read(&hwc->prev_count); + + /* read counter values from all units */ + for (i = 0; i < OCX_FRC_NR_UNITS; i++) { + new = readq(map_offset_ocx_frc(hwc->event_base, uncore, i)); + sum += new & OCX_FRC_EVENT_MASK; + } + + local64_set(&hwc->prev_count, new); + delta = new - prev; + local64_add(delta, &event->count); +} + +static void thunder_uncore_start(struct perf_event *event, int flags) +{ + struct thunder_uncore *uncore = event_to_thunder_uncore(event); + struct hw_perf_event *hwc = &event->hw; + u64 prev, ctl; + int i; + + /* restore counter value divided by units into all counters */ + if (flags & PERF_EF_RELOAD) { + prev = local64_read(&hwc->prev_count); + prev = (prev / uncore->nr_units) & OCX_FRC_EVENT_MASK; + for (i = 0; i < uncore->nr_units; i++) + writeq(prev, map_offset_ocx_frc(hwc->event_base, + uncore, i)); + } + + + hwc->state = 0; + + /* enable counters */ + ctl = readq(hwc->config_base + uncore->pdevs[0].map); + ctl |= 1ULL << OCX_FRC_STAT_CONTROL_BIT; + writeq(ctl, hwc->config_base + uncore->pdevs[0].map); + + perf_event_update_userpage(event); +} + +static void thunder_uncore_stop(struct perf_event *event, int flags) +{ + struct thunder_uncore *uncore = event_to_thunder_uncore(event); + struct hw_perf_event *hwc = &event->hw; + u64 ctl; + + /* disable counters */ + ctl = readq(hwc->config_base + uncore->pdevs[0].map); + ctl &= ~(1ULL << OCX_FRC_STAT_CONTROL_BIT); + writeq(ctl, hwc->config_base + uncore->pdevs[0].map); + + hwc->state |= PERF_HES_STOPPED; + + if ((flags & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) { + thunder_uncore_read_ocx_frc(event); + hwc->state |= PERF_HES_UPTODATE; + } +} + +static int thunder_uncore_add(struct perf_event *event, int flags) +{ + struct thunder_uncore *uncore = event_to_thunder_uncore(event); + struct hw_perf_event *hwc = &event->hw; + int i; + + WARN_ON_ONCE(!uncore); + + /* are we already assigned? */ + if (hwc->idx != -1 && uncore->events[hwc->idx] == event) + goto out; + + for (i = 0; i < uncore->num_counters; i++) { + if (uncore->events[i] == event) { + hwc->idx = i; + goto out; + } + } + + /* counters are 1:1 */ + hwc->idx = -1; + if (cmpxchg(&uncore->events[hwc->config], NULL, event) == NULL) + hwc->idx = hwc->config; + +out: + if (hwc->idx == -1) + return -EBUSY; + + hwc->config_base = OCX_FRC_CONTROL_OFFSET - OCX_FRC_COUNTER_OFFSET; + hwc->event_base = hwc->idx * OCX_FRC_COUNTER_INC; + hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED; + + if (flags & PERF_EF_START) + thunder_uncore_start(event, PERF_EF_RELOAD); + + return 0; +} + +PMU_FORMAT_ATTR(event, "config:0-1"); + +static struct attribute *thunder_ocx_frc_format_attr[] = { + &format_attr_event.attr, + NULL, +}; + +static struct attribute_group thunder_ocx_frc_format_group = { + .name = "format", + .attrs = thunder_ocx_frc_format_attr, +}; + +EVENT_ATTR(stat0, OCX_FRC_EVENT_STAT0); +EVENT_ATTR(stat1, OCX_FRC_EVENT_STAT1); +EVENT_ATTR(stat2, OCX_FRC_EVENT_STAT2); +EVENT_ATTR(stat3, OCX_FRC_EVENT_STAT3); + +static struct attribute *thunder_ocx_frc_events_attr[] = { + EVENT_PTR(stat0), + EVENT_PTR(stat1), + EVENT_PTR(stat2), + EVENT_PTR(stat3), + NULL, +}; + +static struct attribute_group thunder_ocx_frc_events_group = { + .name = "events", + .attrs = thunder_ocx_frc_events_attr, +}; + +static const struct attribute_group *thunder_ocx_frc_attr_groups[] = { + &thunder_uncore_attr_group, + &thunder_ocx_frc_format_group, + &thunder_ocx_frc_events_group, + NULL, +}; + +struct pmu thunder_ocx_frc_pmu = { + .attr_groups = thunder_ocx_frc_attr_groups, + .name = "thunder_ocx_frc", + .event_init = thunder_uncore_event_init, + .add = thunder_uncore_add, + .del = thunder_uncore_del, + .start = thunder_uncore_start, + .stop = thunder_uncore_stop, + .read = thunder_uncore_read_ocx_frc, +}; + +static int event_valid(u64 config) +{ + if (config <= OCX_FRC_EVENT_STAT3) + return 1; + else + return 0; +} + +int __init thunder_uncore_ocx_frc_setup(void) +{ + int ret; + + thunder_uncore_ocx_frc = kzalloc(sizeof(struct thunder_uncore), + GFP_KERNEL); + if (!thunder_uncore_ocx_frc) { + ret = -ENOMEM; + goto fail_nomem; + } + + ret = thunder_uncore_setup(thunder_uncore_ocx_frc, + PCI_DEVICE_ID_THUNDER_OCX, OCX_FRC_COUNTER_OFFSET, + OCX_FRC_CONTROL_OFFSET - OCX_FRC_COUNTER_OFFSET + + sizeof(unsigned long long), + &thunder_ocx_frc_pmu); + if (ret) + goto fail; + + thunder_uncore_ocx_frc->type = OCX_FRC_TYPE; + thunder_uncore_ocx_frc->num_counters = OCX_FRC_NR_COUNTERS; + thunder_uncore_ocx_frc->event_valid = event_valid; + return 0; + +fail: + kfree(thunder_uncore_ocx_frc); +fail_nomem: + return ret; +} -- 1.9.1