Message ID | 1500364799-90518-3-git-send-email-zhangshaokun@hisilicon.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Tue, 18 Jul 2017 15:59:55 +0800 Shaokun Zhang <zhangshaokun@hisilicon.com> wrote: > This patch adds support HiSilicon SoC uncore PMU driver framework and > interfaces. > > Signed-off-by: Shaokun Zhang <zhangshaokun@hisilicon.com> > Signed-off-by: Anurup M <anurup.m@huawei.com> A couple of minor things inline. Jonathan > --- > drivers/perf/Kconfig | 7 + > drivers/perf/Makefile | 1 + > drivers/perf/hisilicon/Makefile | 1 + > drivers/perf/hisilicon/hisi_uncore_pmu.c | 411 +++++++++++++++++++++++++++++++ > drivers/perf/hisilicon/hisi_uncore_pmu.h | 116 +++++++++ > 5 files changed, 536 insertions(+) > create mode 100644 drivers/perf/hisilicon/Makefile > create mode 100644 drivers/perf/hisilicon/hisi_uncore_pmu.c > create mode 100644 drivers/perf/hisilicon/hisi_uncore_pmu.h > > diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig > index e5197ff..78fc4bc 100644 > --- a/drivers/perf/Kconfig > +++ b/drivers/perf/Kconfig > @@ -17,6 +17,13 @@ config ARM_PMU_ACPI > depends on ARM_PMU && ACPI > def_bool y > > +config HISI_PMU > + bool "HiSilicon SoC PMU" > + depends on ARM64 && ACPI > + help > + Support for HiSilicon SoC uncore performance monitoring > + unit (PMU), such as: L3C, HHA and DDRC. > + > config QCOM_L2_PMU > bool "Qualcomm Technologies L2-cache PMU" > depends on ARCH_QCOM && ARM64 && ACPI > diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile > index 6420bd4..41d3342 100644 > --- a/drivers/perf/Makefile > +++ b/drivers/perf/Makefile > @@ -1,5 +1,6 @@ > obj-$(CONFIG_ARM_PMU) += arm_pmu.o arm_pmu_platform.o > obj-$(CONFIG_ARM_PMU_ACPI) += arm_pmu_acpi.o > +obj-$(CONFIG_HISI_PMU) += hisilicon/ > obj-$(CONFIG_QCOM_L2_PMU) += qcom_l2_pmu.o > obj-$(CONFIG_QCOM_L3_PMU) += qcom_l3_pmu.o > obj-$(CONFIG_XGENE_PMU) += xgene_pmu.o > diff --git a/drivers/perf/hisilicon/Makefile b/drivers/perf/hisilicon/Makefile > new file mode 100644 > index 0000000..2783bb3 > --- /dev/null > +++ b/drivers/perf/hisilicon/Makefile > @@ -0,0 +1 @@ > +obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o > diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.c b/drivers/perf/hisilicon/hisi_uncore_pmu.c > new file mode 100644 > index 0000000..4eb04e1 > --- /dev/null > +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.c > @@ -0,0 +1,411 @@ > +/* > + * HiSilicon SoC Hardware event counters support > + * > + * Copyright (C) 2017 Hisilicon Limited > + * Author: Anurup M <anurup.m@huawei.com> > + * Shaokun Zhang <zhangshaokun@hisilicon.com> > + * > + * This code is based on the uncore PMUs like arm-cci and arm-ccn. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + */ > +#include <linux/bitmap.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/perf_event.h> > +#include "hisi_uncore_pmu.h" > + > +/* > + * PMU format attributes > + */ > +ssize_t hisi_format_sysfs_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct dev_ext_attribute *eattr; > + > + eattr = container_of(attr, struct dev_ext_attribute, attr); > + > + return sprintf(buf, "%s\n", (char *)eattr->var); > +} > + > +/* > + * PMU event attributes > + */ > +ssize_t hisi_event_sysfs_show(struct device *dev, > + struct device_attribute *attr, char *page) > +{ > + struct dev_ext_attribute *eattr; > + > + eattr = container_of(attr, struct dev_ext_attribute, attr); > + > + return sprintf(page, "config=0x%lx\n", (unsigned long)eattr->var); > +} > + > +/* > + * sysfs cpumask attributes > + */ > +ssize_t hisi_cpumask_sysfs_show(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct pmu *pmu = dev_get_drvdata(dev); > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); > + > + return cpumap_print_to_pagebuf(true, buf, &hisi_pmu->cpus); > +} > + > +/* Read Super CPU cluster and CPU cluster ID from MPIDR_EL1 */ > +void hisi_read_scl_and_ccl_id(u32 *scl_id, u32 *ccl_id) > +{ > + u64 mpidr; > + > + mpidr = read_cpuid_mpidr(); > + if (mpidr & MPIDR_MT_BITMASK) { > + if (scl_id) > + *scl_id = MPIDR_AFFINITY_LEVEL(mpidr, 3); > + if (ccl_id) > + *ccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 2); > + } else { > + if (scl_id) > + *scl_id = MPIDR_AFFINITY_LEVEL(mpidr, 2); > + if (ccl_id) > + *ccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 1); > + } > +} > + > +static bool hisi_validate_event_group(struct perf_event *event) > +{ > + struct perf_event *sibling, *leader = event->group_leader; > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); > + /* Include count for the event */ > + int counters = 1; > + > + /* > + * We must NOT create groups containing mixed PMUs, although > + * software events are acceptable > + */ > + if (leader->pmu != event->pmu && !is_software_event(leader)) > + return false; > + > + /* Increment counter for the leader */ > + counters++; > + > + list_for_each_entry(sibling, &event->group_leader->sibling_list, > + group_entry) { > + if (is_software_event(sibling)) > + continue; > + if (sibling->pmu != event->pmu) > + return false; > + /* Increment counter for each sibling */ > + counters++; > + } > + > + /* The group can not count events more than the counters in the HW */ > + return counters <= hisi_pmu->num_counters; > +} > + > +int hisi_uncore_pmu_counter_valid(struct hisi_pmu *hisi_pmu, int idx) > +{ > + return (idx >= 0 && idx < hisi_pmu->num_counters); > +} > + > +int hisi_uncore_pmu_get_event_idx(struct perf_event *event) > +{ > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); > + unsigned long *used_mask = hisi_pmu->pmu_events.used_mask; > + u32 num_counters = hisi_pmu->num_counters; > + int idx; > + > + idx = find_first_zero_bit(used_mask, num_counters); > + if (idx == num_counters) > + return -EAGAIN; > + > + set_bit(idx, used_mask); > + > + return idx; > +} > + > +void hisi_uncore_pmu_clear_event_idx(struct hisi_pmu *hisi_pmu, int idx) > +{ > + if (!hisi_uncore_pmu_counter_valid(hisi_pmu, idx)) { > + dev_err(hisi_pmu->dev, "Unsupported event index:%d!\n", idx); > + return; > + } > + > + clear_bit(idx, hisi_pmu->pmu_events.used_mask); > +} > + > +int hisi_uncore_pmu_event_init(struct perf_event *event) > +{ > + struct hisi_pmu *hisi_pmu; > + struct hw_perf_event *hwc = &event->hw; > + int cpu; > + > + if (event->attr.type != event->pmu->type) > + return -ENOENT; > + > + /* > + * We do not support sampling as the counters are all > + * shared by all CPU cores in a CPU die(SCCL). Also we > + * do not support attach to a task(per-process mode) > + */ > + if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) > + return -EOPNOTSUPP; > + > + /* counters do not have these bits */ > + if (event->attr.exclude_user || > + event->attr.exclude_kernel || > + event->attr.exclude_host || > + event->attr.exclude_guest || > + event->attr.exclude_hv || > + event->attr.exclude_idle) > + return -EINVAL; > + > + /* > + * The uncore counters not specific to any CPU, so cannot > + * support per-task > + */ > + if (event->cpu < 0) > + return -EINVAL; > + > + /* > + * Validate if the events in group does not exceed the > + * available counters in hardware. > + */ > + if (!hisi_validate_event_group(event)) > + return -EINVAL; > + > + /* > + * We don't assign an index until we actually place the event onto > + * hardware. Use -1 to signify that we haven't decided where to put it > + * yet. > + */ > + hwc->idx = -1; > + hwc->config_base = event->attr.config; > + > + /* Select an available CPU to monitor events in this PMU */ > + hisi_pmu = to_hisi_pmu(event->pmu); > + cpu = cpumask_first(&hisi_pmu->cpus); > + if (cpu >= nr_cpu_ids) > + return -EINVAL; > + > + /* Enforce to use the same CPU for all events in this PMU */ > + event->cpu = cpu; > + > + return 0; > +} > + > +/* > + * Set the counter to count the event that we're interested in, > + * and enable counter and interrupt. > + */ > +static void hisi_uncore_pmu_enable_event(struct perf_event *event) > +{ > + struct hw_perf_event *hwc = &event->hw; > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); > + > + /* > + * Write event code in event select registers(for DDRC PMU, > + * event has been mapped to fixed-purpose counter, there is > + * no need to write event type). > + */ > + if (hisi_pmu->ops->write_evtype) > + hisi_pmu->ops->write_evtype(hisi_pmu, hwc->idx, > + GET_EVENTID(event)); > + > + /* Enable the hardware event counting and interrupt */ > + hisi_pmu->ops->enable_counter(hisi_pmu, hwc); > + hisi_pmu->ops->enable_counter_int(hisi_pmu, hwc); > +} > + > +/* > + * Disable counting and interrupt. > + */ > +static void hisi_uncore_pmu_disable_event(struct perf_event *event) > +{ > + struct hw_perf_event *hwc = &event->hw; > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); > + > + hisi_pmu->ops->disable_counter(hisi_pmu, hwc); > + hisi_pmu->ops->disable_counter_int(hisi_pmu, hwc); > +} > + > +void hisi_uncore_pmu_set_event_period(struct perf_event *event) > +{ > + struct hw_perf_event *hwc = &event->hw; > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); > + > + /* > + * The HiSilicon PMU counters support 32 bits or 48 bits, depending on > + * the PMU. We reduce it to 2^(counter_bits - 1) to account for the > + * extreme interrupt latency. So we could hopefully handle the overflow > + * interrupt before another 2^(counter_bits - 1) events occur and the > + * counter overtakes its previous value. > + */ > + u64 val = BIT_ULL(hisi_pmu->counter_bits - 1); > + > + local64_set(&hwc->prev_count, val); > + /* Write start value to the hardware event counter */ > + hisi_pmu->ops->write_counter(hisi_pmu, hwc, val); > +} > + > +u64 hisi_uncore_pmu_event_update(struct perf_event *event) > +{ > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); > + struct hw_perf_event *hwc = &event->hw; > + u64 delta, prev_raw_count, new_raw_count; > + > + do { > + /* Read the count from the counter register */ > + new_raw_count = hisi_pmu->ops->read_counter(hisi_pmu, hwc); > + prev_raw_count = local64_read(&hwc->prev_count); > + } while (local64_cmpxchg(&hwc->prev_count, prev_raw_count, > + new_raw_count) != prev_raw_count); > + /* > + * compute the delta > + */ > + delta = (new_raw_count - prev_raw_count) & > + HISI_MAX_PERIOD(hisi_pmu->counter_bits); > + local64_add(delta, &event->count); > + > + return new_raw_count; > +} > + > +void hisi_uncore_pmu_start(struct perf_event *event, int flags) > +{ > + struct hw_perf_event *hwc = &event->hw; > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); > + > + if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED))) > + return; > + > + WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE)); > + hwc->state = 0; > + hisi_uncore_pmu_set_event_period(event); > + > + if (flags & PERF_EF_RELOAD) { > + u64 prev_raw_count = local64_read(&hwc->prev_count); > + > + hisi_pmu->ops->write_counter(hisi_pmu, hwc, prev_raw_count); > + } > + > + hisi_uncore_pmu_enable_event(event); > + perf_event_update_userpage(event); > +} > + > +void hisi_uncore_pmu_stop(struct perf_event *event, int flags) > +{ > + struct hw_perf_event *hwc = &event->hw; > + > + hisi_uncore_pmu_disable_event(event); > + WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED); > + hwc->state |= PERF_HES_STOPPED; > + > + if (hwc->state & PERF_HES_UPTODATE) > + return; > + > + /* Read hardware counter and update the Perf counter statistics */ > + hisi_uncore_pmu_event_update(event); > + hwc->state |= PERF_HES_UPTODATE; > +} > + > +int hisi_uncore_pmu_add(struct perf_event *event, int flags) > +{ > + struct hw_perf_event *hwc = &event->hw; > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); > + int idx; > + > + hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE; > + > + /* Get an available counter index for counting */ > + idx = hisi_pmu->ops->get_event_idx(event); > + if (idx < 0) > + return -EAGAIN; > + > + event->hw.idx = idx; > + hisi_pmu->pmu_events.hw_events[idx] = event; > + > + if (flags & PERF_EF_START) > + hisi_uncore_pmu_start(event, PERF_EF_RELOAD); > + > + return 0; > +} > + > +void hisi_uncore_pmu_del(struct perf_event *event, int flags) > +{ > + struct hw_perf_event *hwc = &event->hw; > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); > + > + hisi_uncore_pmu_stop(event, PERF_EF_UPDATE); > + hisi_uncore_pmu_clear_event_idx(hisi_pmu, hwc->idx); > + perf_event_update_userpage(event); > + hisi_pmu->pmu_events.hw_events[hwc->idx] = NULL; > +} > + > +struct hisi_pmu *hisi_pmu_alloc(struct device *dev, u32 num_cntrs) > +{ > + struct hisi_pmu *hisi_pmu; > + struct hisi_pmu_hwevents *pmu_events; > + > + hisi_pmu = devm_kzalloc(dev, sizeof(*hisi_pmu), GFP_KERNEL); > + if (!hisi_pmu) > + return ERR_PTR(-ENOMEM); > + > + pmu_events = &hisi_pmu->pmu_events; > + pmu_events->hw_events = devm_kcalloc(dev, > + num_cntrs, > + sizeof(*pmu_events->hw_events), > + GFP_KERNEL); > + if (!pmu_events->hw_events) > + return ERR_PTR(-ENOMEM); > + > + pmu_events->used_mask = devm_kcalloc(dev, > + BITS_TO_LONGS(num_cntrs), > + sizeof(*pmu_events->used_mask), > + GFP_KERNEL); > + if (!pmu_events->used_mask) > + return ERR_PTR(-ENOMEM); > + > + return hisi_pmu; > +} > + > +void hisi_uncore_pmu_read(struct perf_event *event) > +{ > + /* Read hardware counter and update the perf counter statistics */ > + hisi_uncore_pmu_event_update(event); > +} > + > +void hisi_uncore_pmu_enable(struct pmu *pmu) > +{ > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); > + int enabled = bitmap_weight(hisi_pmu->pmu_events.used_mask, > + hisi_pmu->num_counters); > + > + if (!enabled) > + return; > + > + hisi_pmu->ops->start_counters(hisi_pmu); > +} > + > +void hisi_uncore_pmu_disable(struct pmu *pmu) > +{ > + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); > + > + hisi_pmu->ops->stop_counters(hisi_pmu); > +} > + > +int hisi_uncore_pmu_setup(struct hisi_pmu *hisi_pmu, const char *pmu_name) > +{ > + /* Register the events with perf */ > + return perf_pmu_register(&hisi_pmu->pmu, pmu_name, -1); > +} This seems oddly unbalanced given you then do the perf_pmu_unregister() calls directly in the various drivers. So I'd push the contents of this function down into the drivers themselves. > diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.h b/drivers/perf/hisilicon/hisi_uncore_pmu.h > new file mode 100644 > index 0000000..eb2abb0 > --- /dev/null > +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.h > @@ -0,0 +1,116 @@ > +/* > + * HiSilicon SoC Hardware event counters support > + * > + * Copyright (C) 2017 Hisilicon Limited > + * Author: Anurup M <anurup.m@huawei.com> > + * Shaokun Zhang <zhangshaokun@hisilicon.com> > + * > + * This code is based on the uncore PMUs like arm-cci and arm-ccn. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see <http://www.gnu.org/licenses/>. > + */ > +#ifndef __HISI_UNCORE_PMU_H__ > +#define __HISI_UNCORE_PMU_H__ > + > +#include <linux/init.h> > +#include <linux/interrupt.h> > +#include <linux/kernel.h> > +#include <linux/ktime.h> > +#include <linux/platform_device.h> > +#include <linux/slab.h> > +#include <linux/types.h> > +#include <asm/local64.h> This is a wide range of headers to have included from an include. At least some of these should be pushed down into the c files. > + > +#undef pr_fmt > +#define pr_fmt(fmt) "hisi_pmu: " fmt > + > +#define GET_EVENTID(ev) (ev->hw.config_base & 0xff) I'd add a prefix to this as chance a clash in future for something with that name seems very high. It is also only used in hisi_uncore_pmu.c so I'd push it down into that file. > +#define to_hisi_pmu(p) (container_of(p, struct hisi_pmu, pmu)) > +#define HISI_MAX_PERIOD(nr) (BIT_ULL(nr) - 1) Only used in one file, so I'd push it down into the c file. > + > +#define HISI_PMU_ATTR(_name, _func, _config) \ > + (&((struct dev_ext_attribute[]) { \ > + { __ATTR(_name, 0444, _func, NULL), (void *)_config } \ > + })[0].attr.attr) > + > +#define HISI_PMU_FORMAT_ATTR(_name, _config) \ > + HISI_PMU_ATTR(_name, hisi_format_sysfs_show, (void *)_config) > +#define HISI_PMU_EVENT_ATTR(_name, _config) \ > + HISI_PMU_ATTR(_name, hisi_event_sysfs_show, (unsigned long)_config) > + > +struct hisi_pmu; > + > +struct hisi_uncore_ops { > + void (*write_evtype)(struct hisi_pmu *, int, u32); > + int (*get_event_idx)(struct perf_event *); > + u64 (*read_counter)(struct hisi_pmu *, struct hw_perf_event *); > + void (*write_counter)(struct hisi_pmu *, struct hw_perf_event *, u64); > + void (*enable_counter)(struct hisi_pmu *, struct hw_perf_event *); > + void (*disable_counter)(struct hisi_pmu *, struct hw_perf_event *); > + void (*enable_counter_int)(struct hisi_pmu *, struct hw_perf_event *); > + void (*disable_counter_int)(struct hisi_pmu *, struct hw_perf_event *); > + void (*start_counters)(struct hisi_pmu *); > + void (*stop_counters)(struct hisi_pmu *); > +}; > + > +struct hisi_pmu_hwevents { > + struct perf_event **hw_events; > + unsigned long *used_mask; > +}; > + > +/* Generic pmu struct for different pmu types */ > +struct hisi_pmu { > + const char *name; > + struct pmu pmu; > + const struct hisi_uncore_ops *ops; > + struct hisi_pmu_hwevents pmu_events; > + cpumask_t cpus; > + struct device *dev; > + struct hlist_node node; > + u32 scl_id; > + u32 ccl_id; > + /* Hardware information for different pmu types */ > + void __iomem *base; > + union { > + u32 ddrc_chn_id; > + u32 l3c_tag_id; > + u32 hha_uid; > + }; > + int num_counters; > + int num_events; > + int counter_bits; > +}; > + > +int hisi_uncore_pmu_counter_valid(struct hisi_pmu *hisi_pmu, int idx); > +int hisi_uncore_pmu_get_event_idx(struct perf_event *event); > +void hisi_uncore_pmu_clear_event_idx(struct hisi_pmu *hisi_pmu, int idx); The above is only used in hisi_uncore_pmu.c so doesn't need to be here and can be static. > +void hisi_uncore_pmu_read(struct perf_event *event); > +int hisi_uncore_pmu_add(struct perf_event *event, int flags); > +void hisi_uncore_pmu_del(struct perf_event *event, int flags); > +void hisi_uncore_pmu_start(struct perf_event *event, int flags); > +void hisi_uncore_pmu_stop(struct perf_event *event, int flags); > +void hisi_uncore_pmu_set_event_period(struct perf_event *event); > +u64 hisi_uncore_pmu_event_update(struct perf_event *event); > +int hisi_uncore_pmu_event_init(struct perf_event *event); > +int hisi_uncore_pmu_setup(struct hisi_pmu *hisi_pmu, const char *pmu_name); > +void hisi_uncore_pmu_enable(struct pmu *pmu); > +void hisi_uncore_pmu_disable(struct pmu *pmu); > +struct hisi_pmu *hisi_pmu_alloc(struct device *dev, u32 num_cntrs); > +ssize_t hisi_event_sysfs_show(struct device *dev, > + struct device_attribute *attr, char *buf); > +ssize_t hisi_format_sysfs_show(struct device *dev, > + struct device_attribute *attr, char *buf); > +ssize_t hisi_cpumask_sysfs_show(struct device *dev, > + struct device_attribute *attr, char *buf); > +void hisi_read_scl_and_ccl_id(u32 *scl_id, u32 *ccl_id); > +#endif /* __HISI_UNCORE_PMU_H__ */
Hi Jonathan On 2017/7/19 17:19, Jonathan Cameron wrote: > On Tue, 18 Jul 2017 15:59:55 +0800 > Shaokun Zhang <zhangshaokun@hisilicon.com> wrote: > >> This patch adds support HiSilicon SoC uncore PMU driver framework and >> interfaces. >> >> Signed-off-by: Shaokun Zhang <zhangshaokun@hisilicon.com> >> Signed-off-by: Anurup M <anurup.m@huawei.com> > A couple of minor things inline. > > Jonathan >> --- >> drivers/perf/Kconfig | 7 + >> drivers/perf/Makefile | 1 + >> drivers/perf/hisilicon/Makefile | 1 + >> drivers/perf/hisilicon/hisi_uncore_pmu.c | 411 +++++++++++++++++++++++++++++++ >> drivers/perf/hisilicon/hisi_uncore_pmu.h | 116 +++++++++ >> 5 files changed, 536 insertions(+) >> create mode 100644 drivers/perf/hisilicon/Makefile >> create mode 100644 drivers/perf/hisilicon/hisi_uncore_pmu.c >> create mode 100644 drivers/perf/hisilicon/hisi_uncore_pmu.h >> >> diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig >> index e5197ff..78fc4bc 100644 >> --- a/drivers/perf/Kconfig >> +++ b/drivers/perf/Kconfig >> @@ -17,6 +17,13 @@ config ARM_PMU_ACPI >> depends on ARM_PMU && ACPI >> def_bool y >> >> +config HISI_PMU >> + bool "HiSilicon SoC PMU" >> + depends on ARM64 && ACPI >> + help >> + Support for HiSilicon SoC uncore performance monitoring >> + unit (PMU), such as: L3C, HHA and DDRC. >> + >> config QCOM_L2_PMU >> bool "Qualcomm Technologies L2-cache PMU" >> depends on ARCH_QCOM && ARM64 && ACPI >> diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile >> index 6420bd4..41d3342 100644 >> --- a/drivers/perf/Makefile >> +++ b/drivers/perf/Makefile >> @@ -1,5 +1,6 @@ >> obj-$(CONFIG_ARM_PMU) += arm_pmu.o arm_pmu_platform.o >> obj-$(CONFIG_ARM_PMU_ACPI) += arm_pmu_acpi.o >> +obj-$(CONFIG_HISI_PMU) += hisilicon/ >> obj-$(CONFIG_QCOM_L2_PMU) += qcom_l2_pmu.o >> obj-$(CONFIG_QCOM_L3_PMU) += qcom_l3_pmu.o >> obj-$(CONFIG_XGENE_PMU) += xgene_pmu.o >> diff --git a/drivers/perf/hisilicon/Makefile b/drivers/perf/hisilicon/Makefile >> new file mode 100644 >> index 0000000..2783bb3 >> --- /dev/null >> +++ b/drivers/perf/hisilicon/Makefile >> @@ -0,0 +1 @@ >> +obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o >> diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.c b/drivers/perf/hisilicon/hisi_uncore_pmu.c >> new file mode 100644 >> index 0000000..4eb04e1 >> --- /dev/null >> +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.c >> @@ -0,0 +1,411 @@ >> +/* >> + * HiSilicon SoC Hardware event counters support >> + * >> + * Copyright (C) 2017 Hisilicon Limited >> + * Author: Anurup M <anurup.m@huawei.com> >> + * Shaokun Zhang <zhangshaokun@hisilicon.com> >> + * >> + * This code is based on the uncore PMUs like arm-cci and arm-ccn. >> + * >> + * This program is free software; you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License version 2 as >> + * published by the Free Software Foundation. >> + * >> + * This program is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + * GNU General Public License for more details. >> + * >> + * You should have received a copy of the GNU General Public License >> + * along with this program. If not, see <http://www.gnu.org/licenses/>. >> + */ >> +#include <linux/bitmap.h> >> +#include <linux/kernel.h> >> +#include <linux/module.h> >> +#include <linux/of.h> >> +#include <linux/perf_event.h> >> +#include "hisi_uncore_pmu.h" >> + >> +/* >> + * PMU format attributes >> + */ >> +ssize_t hisi_format_sysfs_show(struct device *dev, >> + struct device_attribute *attr, char *buf) >> +{ >> + struct dev_ext_attribute *eattr; >> + >> + eattr = container_of(attr, struct dev_ext_attribute, attr); >> + >> + return sprintf(buf, "%s\n", (char *)eattr->var); >> +} >> + >> +/* >> + * PMU event attributes >> + */ >> +ssize_t hisi_event_sysfs_show(struct device *dev, >> + struct device_attribute *attr, char *page) >> +{ >> + struct dev_ext_attribute *eattr; >> + >> + eattr = container_of(attr, struct dev_ext_attribute, attr); >> + >> + return sprintf(page, "config=0x%lx\n", (unsigned long)eattr->var); >> +} >> + >> +/* >> + * sysfs cpumask attributes >> + */ >> +ssize_t hisi_cpumask_sysfs_show(struct device *dev, >> + struct device_attribute *attr, char *buf) >> +{ >> + struct pmu *pmu = dev_get_drvdata(dev); >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); >> + >> + return cpumap_print_to_pagebuf(true, buf, &hisi_pmu->cpus); >> +} >> + >> +/* Read Super CPU cluster and CPU cluster ID from MPIDR_EL1 */ >> +void hisi_read_scl_and_ccl_id(u32 *scl_id, u32 *ccl_id) >> +{ >> + u64 mpidr; >> + >> + mpidr = read_cpuid_mpidr(); >> + if (mpidr & MPIDR_MT_BITMASK) { >> + if (scl_id) >> + *scl_id = MPIDR_AFFINITY_LEVEL(mpidr, 3); >> + if (ccl_id) >> + *ccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 2); >> + } else { >> + if (scl_id) >> + *scl_id = MPIDR_AFFINITY_LEVEL(mpidr, 2); >> + if (ccl_id) >> + *ccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 1); >> + } >> +} >> + >> +static bool hisi_validate_event_group(struct perf_event *event) >> +{ >> + struct perf_event *sibling, *leader = event->group_leader; >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); >> + /* Include count for the event */ >> + int counters = 1; >> + >> + /* >> + * We must NOT create groups containing mixed PMUs, although >> + * software events are acceptable >> + */ >> + if (leader->pmu != event->pmu && !is_software_event(leader)) >> + return false; >> + >> + /* Increment counter for the leader */ >> + counters++; >> + >> + list_for_each_entry(sibling, &event->group_leader->sibling_list, >> + group_entry) { >> + if (is_software_event(sibling)) >> + continue; >> + if (sibling->pmu != event->pmu) >> + return false; >> + /* Increment counter for each sibling */ >> + counters++; >> + } >> + >> + /* The group can not count events more than the counters in the HW */ >> + return counters <= hisi_pmu->num_counters; >> +} >> + >> +int hisi_uncore_pmu_counter_valid(struct hisi_pmu *hisi_pmu, int idx) >> +{ >> + return (idx >= 0 && idx < hisi_pmu->num_counters); >> +} >> + >> +int hisi_uncore_pmu_get_event_idx(struct perf_event *event) >> +{ >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); >> + unsigned long *used_mask = hisi_pmu->pmu_events.used_mask; >> + u32 num_counters = hisi_pmu->num_counters; >> + int idx; >> + >> + idx = find_first_zero_bit(used_mask, num_counters); >> + if (idx == num_counters) >> + return -EAGAIN; >> + >> + set_bit(idx, used_mask); >> + >> + return idx; >> +} >> + >> +void hisi_uncore_pmu_clear_event_idx(struct hisi_pmu *hisi_pmu, int idx) >> +{ >> + if (!hisi_uncore_pmu_counter_valid(hisi_pmu, idx)) { >> + dev_err(hisi_pmu->dev, "Unsupported event index:%d!\n", idx); >> + return; >> + } >> + >> + clear_bit(idx, hisi_pmu->pmu_events.used_mask); >> +} >> + >> +int hisi_uncore_pmu_event_init(struct perf_event *event) >> +{ >> + struct hisi_pmu *hisi_pmu; >> + struct hw_perf_event *hwc = &event->hw; >> + int cpu; >> + >> + if (event->attr.type != event->pmu->type) >> + return -ENOENT; >> + >> + /* >> + * We do not support sampling as the counters are all >> + * shared by all CPU cores in a CPU die(SCCL). Also we >> + * do not support attach to a task(per-process mode) >> + */ >> + if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) >> + return -EOPNOTSUPP; >> + >> + /* counters do not have these bits */ >> + if (event->attr.exclude_user || >> + event->attr.exclude_kernel || >> + event->attr.exclude_host || >> + event->attr.exclude_guest || >> + event->attr.exclude_hv || >> + event->attr.exclude_idle) >> + return -EINVAL; >> + >> + /* >> + * The uncore counters not specific to any CPU, so cannot >> + * support per-task >> + */ >> + if (event->cpu < 0) >> + return -EINVAL; >> + >> + /* >> + * Validate if the events in group does not exceed the >> + * available counters in hardware. >> + */ >> + if (!hisi_validate_event_group(event)) >> + return -EINVAL; >> + >> + /* >> + * We don't assign an index until we actually place the event onto >> + * hardware. Use -1 to signify that we haven't decided where to put it >> + * yet. >> + */ >> + hwc->idx = -1; >> + hwc->config_base = event->attr.config; >> + >> + /* Select an available CPU to monitor events in this PMU */ >> + hisi_pmu = to_hisi_pmu(event->pmu); >> + cpu = cpumask_first(&hisi_pmu->cpus); >> + if (cpu >= nr_cpu_ids) >> + return -EINVAL; >> + >> + /* Enforce to use the same CPU for all events in this PMU */ >> + event->cpu = cpu; >> + >> + return 0; >> +} >> + >> +/* >> + * Set the counter to count the event that we're interested in, >> + * and enable counter and interrupt. >> + */ >> +static void hisi_uncore_pmu_enable_event(struct perf_event *event) >> +{ >> + struct hw_perf_event *hwc = &event->hw; >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); >> + >> + /* >> + * Write event code in event select registers(for DDRC PMU, >> + * event has been mapped to fixed-purpose counter, there is >> + * no need to write event type). >> + */ >> + if (hisi_pmu->ops->write_evtype) >> + hisi_pmu->ops->write_evtype(hisi_pmu, hwc->idx, >> + GET_EVENTID(event)); >> + >> + /* Enable the hardware event counting and interrupt */ >> + hisi_pmu->ops->enable_counter(hisi_pmu, hwc); >> + hisi_pmu->ops->enable_counter_int(hisi_pmu, hwc); >> +} >> + >> +/* >> + * Disable counting and interrupt. >> + */ >> +static void hisi_uncore_pmu_disable_event(struct perf_event *event) >> +{ >> + struct hw_perf_event *hwc = &event->hw; >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); >> + >> + hisi_pmu->ops->disable_counter(hisi_pmu, hwc); >> + hisi_pmu->ops->disable_counter_int(hisi_pmu, hwc); >> +} >> + >> +void hisi_uncore_pmu_set_event_period(struct perf_event *event) >> +{ >> + struct hw_perf_event *hwc = &event->hw; >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); >> + >> + /* >> + * The HiSilicon PMU counters support 32 bits or 48 bits, depending on >> + * the PMU. We reduce it to 2^(counter_bits - 1) to account for the >> + * extreme interrupt latency. So we could hopefully handle the overflow >> + * interrupt before another 2^(counter_bits - 1) events occur and the >> + * counter overtakes its previous value. >> + */ >> + u64 val = BIT_ULL(hisi_pmu->counter_bits - 1); >> + >> + local64_set(&hwc->prev_count, val); >> + /* Write start value to the hardware event counter */ >> + hisi_pmu->ops->write_counter(hisi_pmu, hwc, val); >> +} >> + >> +u64 hisi_uncore_pmu_event_update(struct perf_event *event) >> +{ >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); >> + struct hw_perf_event *hwc = &event->hw; >> + u64 delta, prev_raw_count, new_raw_count; >> + >> + do { >> + /* Read the count from the counter register */ >> + new_raw_count = hisi_pmu->ops->read_counter(hisi_pmu, hwc); >> + prev_raw_count = local64_read(&hwc->prev_count); >> + } while (local64_cmpxchg(&hwc->prev_count, prev_raw_count, >> + new_raw_count) != prev_raw_count); >> + /* >> + * compute the delta >> + */ >> + delta = (new_raw_count - prev_raw_count) & >> + HISI_MAX_PERIOD(hisi_pmu->counter_bits); >> + local64_add(delta, &event->count); >> + >> + return new_raw_count; >> +} >> + >> +void hisi_uncore_pmu_start(struct perf_event *event, int flags) >> +{ >> + struct hw_perf_event *hwc = &event->hw; >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); >> + >> + if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED))) >> + return; >> + >> + WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE)); >> + hwc->state = 0; >> + hisi_uncore_pmu_set_event_period(event); >> + >> + if (flags & PERF_EF_RELOAD) { >> + u64 prev_raw_count = local64_read(&hwc->prev_count); >> + >> + hisi_pmu->ops->write_counter(hisi_pmu, hwc, prev_raw_count); >> + } >> + >> + hisi_uncore_pmu_enable_event(event); >> + perf_event_update_userpage(event); >> +} >> + >> +void hisi_uncore_pmu_stop(struct perf_event *event, int flags) >> +{ >> + struct hw_perf_event *hwc = &event->hw; >> + >> + hisi_uncore_pmu_disable_event(event); >> + WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED); >> + hwc->state |= PERF_HES_STOPPED; >> + >> + if (hwc->state & PERF_HES_UPTODATE) >> + return; >> + >> + /* Read hardware counter and update the Perf counter statistics */ >> + hisi_uncore_pmu_event_update(event); >> + hwc->state |= PERF_HES_UPTODATE; >> +} >> + >> +int hisi_uncore_pmu_add(struct perf_event *event, int flags) >> +{ >> + struct hw_perf_event *hwc = &event->hw; >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); >> + int idx; >> + >> + hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE; >> + >> + /* Get an available counter index for counting */ >> + idx = hisi_pmu->ops->get_event_idx(event); >> + if (idx < 0) >> + return -EAGAIN; >> + >> + event->hw.idx = idx; >> + hisi_pmu->pmu_events.hw_events[idx] = event; >> + >> + if (flags & PERF_EF_START) >> + hisi_uncore_pmu_start(event, PERF_EF_RELOAD); >> + >> + return 0; >> +} >> + >> +void hisi_uncore_pmu_del(struct perf_event *event, int flags) >> +{ >> + struct hw_perf_event *hwc = &event->hw; >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); >> + >> + hisi_uncore_pmu_stop(event, PERF_EF_UPDATE); >> + hisi_uncore_pmu_clear_event_idx(hisi_pmu, hwc->idx); >> + perf_event_update_userpage(event); >> + hisi_pmu->pmu_events.hw_events[hwc->idx] = NULL; >> +} >> + >> +struct hisi_pmu *hisi_pmu_alloc(struct device *dev, u32 num_cntrs) >> +{ >> + struct hisi_pmu *hisi_pmu; >> + struct hisi_pmu_hwevents *pmu_events; >> + >> + hisi_pmu = devm_kzalloc(dev, sizeof(*hisi_pmu), GFP_KERNEL); >> + if (!hisi_pmu) >> + return ERR_PTR(-ENOMEM); >> + >> + pmu_events = &hisi_pmu->pmu_events; >> + pmu_events->hw_events = devm_kcalloc(dev, >> + num_cntrs, >> + sizeof(*pmu_events->hw_events), >> + GFP_KERNEL); >> + if (!pmu_events->hw_events) >> + return ERR_PTR(-ENOMEM); >> + >> + pmu_events->used_mask = devm_kcalloc(dev, >> + BITS_TO_LONGS(num_cntrs), >> + sizeof(*pmu_events->used_mask), >> + GFP_KERNEL); >> + if (!pmu_events->used_mask) >> + return ERR_PTR(-ENOMEM); >> + >> + return hisi_pmu; >> +} >> + >> +void hisi_uncore_pmu_read(struct perf_event *event) >> +{ >> + /* Read hardware counter and update the perf counter statistics */ >> + hisi_uncore_pmu_event_update(event); >> +} >> + >> +void hisi_uncore_pmu_enable(struct pmu *pmu) >> +{ >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); >> + int enabled = bitmap_weight(hisi_pmu->pmu_events.used_mask, >> + hisi_pmu->num_counters); >> + >> + if (!enabled) >> + return; >> + >> + hisi_pmu->ops->start_counters(hisi_pmu); >> +} >> + >> +void hisi_uncore_pmu_disable(struct pmu *pmu) >> +{ >> + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); >> + >> + hisi_pmu->ops->stop_counters(hisi_pmu); >> +} >> + >> +int hisi_uncore_pmu_setup(struct hisi_pmu *hisi_pmu, const char *pmu_name) >> +{ >> + /* Register the events with perf */ >> + return perf_pmu_register(&hisi_pmu->pmu, pmu_name, -1); >> +} > This seems oddly unbalanced given you then do the > perf_pmu_unregister() calls directly in the various drivers. > > So I'd push the contents of this function down into the drivers > themselves. > Agree, shall remove this function call perf_pmu_unregister() directly in drivers. >> diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.h b/drivers/perf/hisilicon/hisi_uncore_pmu.h >> new file mode 100644 >> index 0000000..eb2abb0 >> --- /dev/null >> +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.h >> @@ -0,0 +1,116 @@ >> +/* >> + * HiSilicon SoC Hardware event counters support >> + * >> + * Copyright (C) 2017 Hisilicon Limited >> + * Author: Anurup M <anurup.m@huawei.com> >> + * Shaokun Zhang <zhangshaokun@hisilicon.com> >> + * >> + * This code is based on the uncore PMUs like arm-cci and arm-ccn. >> + * >> + * This program is free software; you can redistribute it and/or modify >> + * it under the terms of the GNU General Public License version 2 as >> + * published by the Free Software Foundation. >> + * >> + * This program is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + * GNU General Public License for more details. >> + * >> + * You should have received a copy of the GNU General Public License >> + * along with this program. If not, see <http://www.gnu.org/licenses/>. >> + */ >> +#ifndef __HISI_UNCORE_PMU_H__ >> +#define __HISI_UNCORE_PMU_H__ >> + >> +#include <linux/init.h> >> +#include <linux/interrupt.h> >> +#include <linux/kernel.h> >> +#include <linux/ktime.h> >> +#include <linux/platform_device.h> >> +#include <linux/slab.h> >> +#include <linux/types.h> >> +#include <asm/local64.h> > This is a wide range of headers to have included from > an include. At least some of these should be pushed down into the c files. > Ok, shall organize the header file. >> + >> +#undef pr_fmt >> +#define pr_fmt(fmt) "hisi_pmu: " fmt >> + >> +#define GET_EVENTID(ev) (ev->hw.config_base & 0xff) > I'd add a prefix to this as chance a clash in future for > something with that name seems very high. > > It is also only used in hisi_uncore_pmu.c so I'd push it down into > that file. > Ok, shall fix it. >> +#define to_hisi_pmu(p) (container_of(p, struct hisi_pmu, pmu)) >> +#define HISI_MAX_PERIOD(nr) (BIT_ULL(nr) - 1) > Only used in one file, so I'd push it down into the c file. OK. >> + >> +#define HISI_PMU_ATTR(_name, _func, _config) \ >> + (&((struct dev_ext_attribute[]) { \ >> + { __ATTR(_name, 0444, _func, NULL), (void *)_config } \ >> + })[0].attr.attr) >> + >> +#define HISI_PMU_FORMAT_ATTR(_name, _config) \ >> + HISI_PMU_ATTR(_name, hisi_format_sysfs_show, (void *)_config) >> +#define HISI_PMU_EVENT_ATTR(_name, _config) \ >> + HISI_PMU_ATTR(_name, hisi_event_sysfs_show, (unsigned long)_config) >> + >> +struct hisi_pmu; >> + >> +struct hisi_uncore_ops { >> + void (*write_evtype)(struct hisi_pmu *, int, u32); >> + int (*get_event_idx)(struct perf_event *); >> + u64 (*read_counter)(struct hisi_pmu *, struct hw_perf_event *); >> + void (*write_counter)(struct hisi_pmu *, struct hw_perf_event *, u64); >> + void (*enable_counter)(struct hisi_pmu *, struct hw_perf_event *); >> + void (*disable_counter)(struct hisi_pmu *, struct hw_perf_event *); >> + void (*enable_counter_int)(struct hisi_pmu *, struct hw_perf_event *); >> + void (*disable_counter_int)(struct hisi_pmu *, struct hw_perf_event *); >> + void (*start_counters)(struct hisi_pmu *); >> + void (*stop_counters)(struct hisi_pmu *); >> +}; >> + >> +struct hisi_pmu_hwevents { >> + struct perf_event **hw_events; >> + unsigned long *used_mask; >> +}; >> + >> +/* Generic pmu struct for different pmu types */ >> +struct hisi_pmu { >> + const char *name; >> + struct pmu pmu; >> + const struct hisi_uncore_ops *ops; >> + struct hisi_pmu_hwevents pmu_events; >> + cpumask_t cpus; >> + struct device *dev; >> + struct hlist_node node; >> + u32 scl_id; >> + u32 ccl_id; >> + /* Hardware information for different pmu types */ >> + void __iomem *base; >> + union { >> + u32 ddrc_chn_id; >> + u32 l3c_tag_id; >> + u32 hha_uid; >> + }; >> + int num_counters; >> + int num_events; >> + int counter_bits; >> +}; >> + >> +int hisi_uncore_pmu_counter_valid(struct hisi_pmu *hisi_pmu, int idx); >> +int hisi_uncore_pmu_get_event_idx(struct perf_event *event); >> +void hisi_uncore_pmu_clear_event_idx(struct hisi_pmu *hisi_pmu, int idx); > The above is only used in hisi_uncore_pmu.c so doesn't need to be here > and can be static. > These functions would be called in L3C/HHA/DDR PMU driver. We want to give a uncore perf framework in hisi_uncore_pmu.c for hisilicon uncore PMUs. Thanks. Shaokun >> +void hisi_uncore_pmu_read(struct perf_event *event); >> +int hisi_uncore_pmu_add(struct perf_event *event, int flags); >> +void hisi_uncore_pmu_del(struct perf_event *event, int flags); >> +void hisi_uncore_pmu_start(struct perf_event *event, int flags); >> +void hisi_uncore_pmu_stop(struct perf_event *event, int flags); >> +void hisi_uncore_pmu_set_event_period(struct perf_event *event); >> +u64 hisi_uncore_pmu_event_update(struct perf_event *event); >> +int hisi_uncore_pmu_event_init(struct perf_event *event); >> +int hisi_uncore_pmu_setup(struct hisi_pmu *hisi_pmu, const char *pmu_name); >> +void hisi_uncore_pmu_enable(struct pmu *pmu); >> +void hisi_uncore_pmu_disable(struct pmu *pmu); >> +struct hisi_pmu *hisi_pmu_alloc(struct device *dev, u32 num_cntrs); >> +ssize_t hisi_event_sysfs_show(struct device *dev, >> + struct device_attribute *attr, char *buf); >> +ssize_t hisi_format_sysfs_show(struct device *dev, >> + struct device_attribute *attr, char *buf); >> +ssize_t hisi_cpumask_sysfs_show(struct device *dev, >> + struct device_attribute *attr, char *buf); >> +void hisi_read_scl_and_ccl_id(u32 *scl_id, u32 *ccl_id); >> +#endif /* __HISI_UNCORE_PMU_H__ */ > > > . >
On Thu, 20 Jul 2017 21:03:19 +0800 Zhangshaokun <zhangshaokun@hisilicon.com> wrote: > Hi Jonathan > > On 2017/7/19 17:19, Jonathan Cameron wrote: > > On Tue, 18 Jul 2017 15:59:55 +0800 > > Shaokun Zhang <zhangshaokun@hisilicon.com> wrote: > > > >> This patch adds support HiSilicon SoC uncore PMU driver framework and > >> interfaces. > >> > >> Signed-off-by: Shaokun Zhang <zhangshaokun@hisilicon.com> > >> Signed-off-by: Anurup M <anurup.m@huawei.com> > > A couple of minor things inline. > > <snip> > >> +/* Generic pmu struct for different pmu types */ > >> +struct hisi_pmu { > >> + const char *name; > >> + struct pmu pmu; > >> + const struct hisi_uncore_ops *ops; > >> + struct hisi_pmu_hwevents pmu_events; > >> + cpumask_t cpus; > >> + struct device *dev; > >> + struct hlist_node node; > >> + u32 scl_id; > >> + u32 ccl_id; > >> + /* Hardware information for different pmu types */ > >> + void __iomem *base; > >> + union { > >> + u32 ddrc_chn_id; > >> + u32 l3c_tag_id; > >> + u32 hha_uid; > >> + }; > >> + int num_counters; > >> + int num_events; > >> + int counter_bits; > >> +}; > >> + > >> +int hisi_uncore_pmu_counter_valid(struct hisi_pmu *hisi_pmu, int idx); > >> +int hisi_uncore_pmu_get_event_idx(struct perf_event *event); > >> +void hisi_uncore_pmu_clear_event_idx(struct hisi_pmu *hisi_pmu, int idx); > > The above is only used in hisi_uncore_pmu.c so doesn't need to be here > > and can be static. > > > > These functions would be called in L3C/HHA/DDR PMU driver. We want to give a > uncore perf framework in hisi_uncore_pmu.c for hisilicon uncore PMUs. > For all but the one function above that is true. I couldn't find this one being used anywhere in those drivers. > Thanks. > Shaokun > > >> +void hisi_uncore_pmu_read(struct perf_event *event); > >> +int hisi_uncore_pmu_add(struct perf_event *event, int flags); > >> +void hisi_uncore_pmu_del(struct perf_event *event, int flags); > >> +void hisi_uncore_pmu_start(struct perf_event *event, int flags); > >> +void hisi_uncore_pmu_stop(struct perf_event *event, int flags); > >> +void hisi_uncore_pmu_set_event_period(struct perf_event *event); > >> +u64 hisi_uncore_pmu_event_update(struct perf_event *event); > >> +int hisi_uncore_pmu_event_init(struct perf_event *event); > >> +int hisi_uncore_pmu_setup(struct hisi_pmu *hisi_pmu, const char *pmu_name); > >> +void hisi_uncore_pmu_enable(struct pmu *pmu); > >> +void hisi_uncore_pmu_disable(struct pmu *pmu); > >> +struct hisi_pmu *hisi_pmu_alloc(struct device *dev, u32 num_cntrs); > >> +ssize_t hisi_event_sysfs_show(struct device *dev, > >> + struct device_attribute *attr, char *buf); > >> +ssize_t hisi_format_sysfs_show(struct device *dev, > >> + struct device_attribute *attr, char *buf); > >> +ssize_t hisi_cpumask_sysfs_show(struct device *dev, > >> + struct device_attribute *attr, char *buf); > >> +void hisi_read_scl_and_ccl_id(u32 *scl_id, u32 *ccl_id); > >> +#endif /* __HISI_UNCORE_PMU_H__ */ > > > > > > . > > >
Hi Jonathan On 2017/7/20 21:49, Jonathan Cameron wrote: > On Thu, 20 Jul 2017 21:03:19 +0800 > Zhangshaokun <zhangshaokun@hisilicon.com> wrote: > >> Hi Jonathan >> >> On 2017/7/19 17:19, Jonathan Cameron wrote: >>> On Tue, 18 Jul 2017 15:59:55 +0800 >>> Shaokun Zhang <zhangshaokun@hisilicon.com> wrote: >>> >>>> This patch adds support HiSilicon SoC uncore PMU driver framework and >>>> interfaces. >>>> >>>> Signed-off-by: Shaokun Zhang <zhangshaokun@hisilicon.com> >>>> Signed-off-by: Anurup M <anurup.m@huawei.com> >>> A couple of minor things inline. >>> > <snip> >>>> +/* Generic pmu struct for different pmu types */ >>>> +struct hisi_pmu { >>>> + const char *name; >>>> + struct pmu pmu; >>>> + const struct hisi_uncore_ops *ops; >>>> + struct hisi_pmu_hwevents pmu_events; >>>> + cpumask_t cpus; >>>> + struct device *dev; >>>> + struct hlist_node node; >>>> + u32 scl_id; >>>> + u32 ccl_id; >>>> + /* Hardware information for different pmu types */ >>>> + void __iomem *base; >>>> + union { >>>> + u32 ddrc_chn_id; >>>> + u32 l3c_tag_id; >>>> + u32 hha_uid; >>>> + }; >>>> + int num_counters; >>>> + int num_events; >>>> + int counter_bits; >>>> +}; >>>> + >>>> +int hisi_uncore_pmu_counter_valid(struct hisi_pmu *hisi_pmu, int idx); >>>> +int hisi_uncore_pmu_get_event_idx(struct perf_event *event); >>>> +void hisi_uncore_pmu_clear_event_idx(struct hisi_pmu *hisi_pmu, int idx); >>> The above is only used in hisi_uncore_pmu.c so doesn't need to be here >>> and can be static. >>> >> >> These functions would be called in L3C/HHA/DDR PMU driver. We want to give a >> uncore perf framework in hisi_uncore_pmu.c for hisilicon uncore PMUs. >> > For all but the one function above that is true. I couldn't find this one being > used anywhere in those drivers. > My apologies, it is what your said, thanks. > >> Thanks. >> Shaokun >> >>>> +void hisi_uncore_pmu_read(struct perf_event *event); >>>> +int hisi_uncore_pmu_add(struct perf_event *event, int flags); >>>> +void hisi_uncore_pmu_del(struct perf_event *event, int flags); >>>> +void hisi_uncore_pmu_start(struct perf_event *event, int flags); >>>> +void hisi_uncore_pmu_stop(struct perf_event *event, int flags); >>>> +void hisi_uncore_pmu_set_event_period(struct perf_event *event); >>>> +u64 hisi_uncore_pmu_event_update(struct perf_event *event); >>>> +int hisi_uncore_pmu_event_init(struct perf_event *event); >>>> +int hisi_uncore_pmu_setup(struct hisi_pmu *hisi_pmu, const char *pmu_name); >>>> +void hisi_uncore_pmu_enable(struct pmu *pmu); >>>> +void hisi_uncore_pmu_disable(struct pmu *pmu); >>>> +struct hisi_pmu *hisi_pmu_alloc(struct device *dev, u32 num_cntrs); >>>> +ssize_t hisi_event_sysfs_show(struct device *dev, >>>> + struct device_attribute *attr, char *buf); >>>> +ssize_t hisi_format_sysfs_show(struct device *dev, >>>> + struct device_attribute *attr, char *buf); >>>> +ssize_t hisi_cpumask_sysfs_show(struct device *dev, >>>> + struct device_attribute *attr, char *buf); >>>> +void hisi_read_scl_and_ccl_id(u32 *scl_id, u32 *ccl_id); >>>> +#endif /* __HISI_UNCORE_PMU_H__ */ >>> >>> >>> . >>> >> > > > > . >
diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig index e5197ff..78fc4bc 100644 --- a/drivers/perf/Kconfig +++ b/drivers/perf/Kconfig @@ -17,6 +17,13 @@ config ARM_PMU_ACPI depends on ARM_PMU && ACPI def_bool y +config HISI_PMU + bool "HiSilicon SoC PMU" + depends on ARM64 && ACPI + help + Support for HiSilicon SoC uncore performance monitoring + unit (PMU), such as: L3C, HHA and DDRC. + config QCOM_L2_PMU bool "Qualcomm Technologies L2-cache PMU" depends on ARCH_QCOM && ARM64 && ACPI diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile index 6420bd4..41d3342 100644 --- a/drivers/perf/Makefile +++ b/drivers/perf/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_ARM_PMU) += arm_pmu.o arm_pmu_platform.o obj-$(CONFIG_ARM_PMU_ACPI) += arm_pmu_acpi.o +obj-$(CONFIG_HISI_PMU) += hisilicon/ obj-$(CONFIG_QCOM_L2_PMU) += qcom_l2_pmu.o obj-$(CONFIG_QCOM_L3_PMU) += qcom_l3_pmu.o obj-$(CONFIG_XGENE_PMU) += xgene_pmu.o diff --git a/drivers/perf/hisilicon/Makefile b/drivers/perf/hisilicon/Makefile new file mode 100644 index 0000000..2783bb3 --- /dev/null +++ b/drivers/perf/hisilicon/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.c b/drivers/perf/hisilicon/hisi_uncore_pmu.c new file mode 100644 index 0000000..4eb04e1 --- /dev/null +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.c @@ -0,0 +1,411 @@ +/* + * HiSilicon SoC Hardware event counters support + * + * Copyright (C) 2017 Hisilicon Limited + * Author: Anurup M <anurup.m@huawei.com> + * Shaokun Zhang <zhangshaokun@hisilicon.com> + * + * This code is based on the uncore PMUs like arm-cci and arm-ccn. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/bitmap.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/perf_event.h> +#include "hisi_uncore_pmu.h" + +/* + * PMU format attributes + */ +ssize_t hisi_format_sysfs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *eattr; + + eattr = container_of(attr, struct dev_ext_attribute, attr); + + return sprintf(buf, "%s\n", (char *)eattr->var); +} + +/* + * PMU event attributes + */ +ssize_t hisi_event_sysfs_show(struct device *dev, + struct device_attribute *attr, char *page) +{ + struct dev_ext_attribute *eattr; + + eattr = container_of(attr, struct dev_ext_attribute, attr); + + return sprintf(page, "config=0x%lx\n", (unsigned long)eattr->var); +} + +/* + * sysfs cpumask attributes + */ +ssize_t hisi_cpumask_sysfs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pmu *pmu = dev_get_drvdata(dev); + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); + + return cpumap_print_to_pagebuf(true, buf, &hisi_pmu->cpus); +} + +/* Read Super CPU cluster and CPU cluster ID from MPIDR_EL1 */ +void hisi_read_scl_and_ccl_id(u32 *scl_id, u32 *ccl_id) +{ + u64 mpidr; + + mpidr = read_cpuid_mpidr(); + if (mpidr & MPIDR_MT_BITMASK) { + if (scl_id) + *scl_id = MPIDR_AFFINITY_LEVEL(mpidr, 3); + if (ccl_id) + *ccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 2); + } else { + if (scl_id) + *scl_id = MPIDR_AFFINITY_LEVEL(mpidr, 2); + if (ccl_id) + *ccl_id = MPIDR_AFFINITY_LEVEL(mpidr, 1); + } +} + +static bool hisi_validate_event_group(struct perf_event *event) +{ + struct perf_event *sibling, *leader = event->group_leader; + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); + /* Include count for the event */ + int counters = 1; + + /* + * We must NOT create groups containing mixed PMUs, although + * software events are acceptable + */ + if (leader->pmu != event->pmu && !is_software_event(leader)) + return false; + + /* Increment counter for the leader */ + counters++; + + list_for_each_entry(sibling, &event->group_leader->sibling_list, + group_entry) { + if (is_software_event(sibling)) + continue; + if (sibling->pmu != event->pmu) + return false; + /* Increment counter for each sibling */ + counters++; + } + + /* The group can not count events more than the counters in the HW */ + return counters <= hisi_pmu->num_counters; +} + +int hisi_uncore_pmu_counter_valid(struct hisi_pmu *hisi_pmu, int idx) +{ + return (idx >= 0 && idx < hisi_pmu->num_counters); +} + +int hisi_uncore_pmu_get_event_idx(struct perf_event *event) +{ + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); + unsigned long *used_mask = hisi_pmu->pmu_events.used_mask; + u32 num_counters = hisi_pmu->num_counters; + int idx; + + idx = find_first_zero_bit(used_mask, num_counters); + if (idx == num_counters) + return -EAGAIN; + + set_bit(idx, used_mask); + + return idx; +} + +void hisi_uncore_pmu_clear_event_idx(struct hisi_pmu *hisi_pmu, int idx) +{ + if (!hisi_uncore_pmu_counter_valid(hisi_pmu, idx)) { + dev_err(hisi_pmu->dev, "Unsupported event index:%d!\n", idx); + return; + } + + clear_bit(idx, hisi_pmu->pmu_events.used_mask); +} + +int hisi_uncore_pmu_event_init(struct perf_event *event) +{ + struct hisi_pmu *hisi_pmu; + struct hw_perf_event *hwc = &event->hw; + int cpu; + + if (event->attr.type != event->pmu->type) + return -ENOENT; + + /* + * We do not support sampling as the counters are all + * shared by all CPU cores in a CPU die(SCCL). Also we + * do not support attach to a task(per-process mode) + */ + if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) + return -EOPNOTSUPP; + + /* counters do not have these bits */ + if (event->attr.exclude_user || + event->attr.exclude_kernel || + event->attr.exclude_host || + event->attr.exclude_guest || + event->attr.exclude_hv || + event->attr.exclude_idle) + return -EINVAL; + + /* + * The uncore counters not specific to any CPU, so cannot + * support per-task + */ + if (event->cpu < 0) + return -EINVAL; + + /* + * Validate if the events in group does not exceed the + * available counters in hardware. + */ + if (!hisi_validate_event_group(event)) + return -EINVAL; + + /* + * We don't assign an index until we actually place the event onto + * hardware. Use -1 to signify that we haven't decided where to put it + * yet. + */ + hwc->idx = -1; + hwc->config_base = event->attr.config; + + /* Select an available CPU to monitor events in this PMU */ + hisi_pmu = to_hisi_pmu(event->pmu); + cpu = cpumask_first(&hisi_pmu->cpus); + if (cpu >= nr_cpu_ids) + return -EINVAL; + + /* Enforce to use the same CPU for all events in this PMU */ + event->cpu = cpu; + + return 0; +} + +/* + * Set the counter to count the event that we're interested in, + * and enable counter and interrupt. + */ +static void hisi_uncore_pmu_enable_event(struct perf_event *event) +{ + struct hw_perf_event *hwc = &event->hw; + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); + + /* + * Write event code in event select registers(for DDRC PMU, + * event has been mapped to fixed-purpose counter, there is + * no need to write event type). + */ + if (hisi_pmu->ops->write_evtype) + hisi_pmu->ops->write_evtype(hisi_pmu, hwc->idx, + GET_EVENTID(event)); + + /* Enable the hardware event counting and interrupt */ + hisi_pmu->ops->enable_counter(hisi_pmu, hwc); + hisi_pmu->ops->enable_counter_int(hisi_pmu, hwc); +} + +/* + * Disable counting and interrupt. + */ +static void hisi_uncore_pmu_disable_event(struct perf_event *event) +{ + struct hw_perf_event *hwc = &event->hw; + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); + + hisi_pmu->ops->disable_counter(hisi_pmu, hwc); + hisi_pmu->ops->disable_counter_int(hisi_pmu, hwc); +} + +void hisi_uncore_pmu_set_event_period(struct perf_event *event) +{ + struct hw_perf_event *hwc = &event->hw; + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); + + /* + * The HiSilicon PMU counters support 32 bits or 48 bits, depending on + * the PMU. We reduce it to 2^(counter_bits - 1) to account for the + * extreme interrupt latency. So we could hopefully handle the overflow + * interrupt before another 2^(counter_bits - 1) events occur and the + * counter overtakes its previous value. + */ + u64 val = BIT_ULL(hisi_pmu->counter_bits - 1); + + local64_set(&hwc->prev_count, val); + /* Write start value to the hardware event counter */ + hisi_pmu->ops->write_counter(hisi_pmu, hwc, val); +} + +u64 hisi_uncore_pmu_event_update(struct perf_event *event) +{ + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); + struct hw_perf_event *hwc = &event->hw; + u64 delta, prev_raw_count, new_raw_count; + + do { + /* Read the count from the counter register */ + new_raw_count = hisi_pmu->ops->read_counter(hisi_pmu, hwc); + prev_raw_count = local64_read(&hwc->prev_count); + } while (local64_cmpxchg(&hwc->prev_count, prev_raw_count, + new_raw_count) != prev_raw_count); + /* + * compute the delta + */ + delta = (new_raw_count - prev_raw_count) & + HISI_MAX_PERIOD(hisi_pmu->counter_bits); + local64_add(delta, &event->count); + + return new_raw_count; +} + +void hisi_uncore_pmu_start(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); + + if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED))) + return; + + WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE)); + hwc->state = 0; + hisi_uncore_pmu_set_event_period(event); + + if (flags & PERF_EF_RELOAD) { + u64 prev_raw_count = local64_read(&hwc->prev_count); + + hisi_pmu->ops->write_counter(hisi_pmu, hwc, prev_raw_count); + } + + hisi_uncore_pmu_enable_event(event); + perf_event_update_userpage(event); +} + +void hisi_uncore_pmu_stop(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + + hisi_uncore_pmu_disable_event(event); + WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED); + hwc->state |= PERF_HES_STOPPED; + + if (hwc->state & PERF_HES_UPTODATE) + return; + + /* Read hardware counter and update the Perf counter statistics */ + hisi_uncore_pmu_event_update(event); + hwc->state |= PERF_HES_UPTODATE; +} + +int hisi_uncore_pmu_add(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); + int idx; + + hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE; + + /* Get an available counter index for counting */ + idx = hisi_pmu->ops->get_event_idx(event); + if (idx < 0) + return -EAGAIN; + + event->hw.idx = idx; + hisi_pmu->pmu_events.hw_events[idx] = event; + + if (flags & PERF_EF_START) + hisi_uncore_pmu_start(event, PERF_EF_RELOAD); + + return 0; +} + +void hisi_uncore_pmu_del(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + struct hisi_pmu *hisi_pmu = to_hisi_pmu(event->pmu); + + hisi_uncore_pmu_stop(event, PERF_EF_UPDATE); + hisi_uncore_pmu_clear_event_idx(hisi_pmu, hwc->idx); + perf_event_update_userpage(event); + hisi_pmu->pmu_events.hw_events[hwc->idx] = NULL; +} + +struct hisi_pmu *hisi_pmu_alloc(struct device *dev, u32 num_cntrs) +{ + struct hisi_pmu *hisi_pmu; + struct hisi_pmu_hwevents *pmu_events; + + hisi_pmu = devm_kzalloc(dev, sizeof(*hisi_pmu), GFP_KERNEL); + if (!hisi_pmu) + return ERR_PTR(-ENOMEM); + + pmu_events = &hisi_pmu->pmu_events; + pmu_events->hw_events = devm_kcalloc(dev, + num_cntrs, + sizeof(*pmu_events->hw_events), + GFP_KERNEL); + if (!pmu_events->hw_events) + return ERR_PTR(-ENOMEM); + + pmu_events->used_mask = devm_kcalloc(dev, + BITS_TO_LONGS(num_cntrs), + sizeof(*pmu_events->used_mask), + GFP_KERNEL); + if (!pmu_events->used_mask) + return ERR_PTR(-ENOMEM); + + return hisi_pmu; +} + +void hisi_uncore_pmu_read(struct perf_event *event) +{ + /* Read hardware counter and update the perf counter statistics */ + hisi_uncore_pmu_event_update(event); +} + +void hisi_uncore_pmu_enable(struct pmu *pmu) +{ + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); + int enabled = bitmap_weight(hisi_pmu->pmu_events.used_mask, + hisi_pmu->num_counters); + + if (!enabled) + return; + + hisi_pmu->ops->start_counters(hisi_pmu); +} + +void hisi_uncore_pmu_disable(struct pmu *pmu) +{ + struct hisi_pmu *hisi_pmu = to_hisi_pmu(pmu); + + hisi_pmu->ops->stop_counters(hisi_pmu); +} + +int hisi_uncore_pmu_setup(struct hisi_pmu *hisi_pmu, const char *pmu_name) +{ + /* Register the events with perf */ + return perf_pmu_register(&hisi_pmu->pmu, pmu_name, -1); +} diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.h b/drivers/perf/hisilicon/hisi_uncore_pmu.h new file mode 100644 index 0000000..eb2abb0 --- /dev/null +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.h @@ -0,0 +1,116 @@ +/* + * HiSilicon SoC Hardware event counters support + * + * Copyright (C) 2017 Hisilicon Limited + * Author: Anurup M <anurup.m@huawei.com> + * Shaokun Zhang <zhangshaokun@hisilicon.com> + * + * This code is based on the uncore PMUs like arm-cci and arm-ccn. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#ifndef __HISI_UNCORE_PMU_H__ +#define __HISI_UNCORE_PMU_H__ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/ktime.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <asm/local64.h> + +#undef pr_fmt +#define pr_fmt(fmt) "hisi_pmu: " fmt + +#define GET_EVENTID(ev) (ev->hw.config_base & 0xff) +#define to_hisi_pmu(p) (container_of(p, struct hisi_pmu, pmu)) +#define HISI_MAX_PERIOD(nr) (BIT_ULL(nr) - 1) + +#define HISI_PMU_ATTR(_name, _func, _config) \ + (&((struct dev_ext_attribute[]) { \ + { __ATTR(_name, 0444, _func, NULL), (void *)_config } \ + })[0].attr.attr) + +#define HISI_PMU_FORMAT_ATTR(_name, _config) \ + HISI_PMU_ATTR(_name, hisi_format_sysfs_show, (void *)_config) +#define HISI_PMU_EVENT_ATTR(_name, _config) \ + HISI_PMU_ATTR(_name, hisi_event_sysfs_show, (unsigned long)_config) + +struct hisi_pmu; + +struct hisi_uncore_ops { + void (*write_evtype)(struct hisi_pmu *, int, u32); + int (*get_event_idx)(struct perf_event *); + u64 (*read_counter)(struct hisi_pmu *, struct hw_perf_event *); + void (*write_counter)(struct hisi_pmu *, struct hw_perf_event *, u64); + void (*enable_counter)(struct hisi_pmu *, struct hw_perf_event *); + void (*disable_counter)(struct hisi_pmu *, struct hw_perf_event *); + void (*enable_counter_int)(struct hisi_pmu *, struct hw_perf_event *); + void (*disable_counter_int)(struct hisi_pmu *, struct hw_perf_event *); + void (*start_counters)(struct hisi_pmu *); + void (*stop_counters)(struct hisi_pmu *); +}; + +struct hisi_pmu_hwevents { + struct perf_event **hw_events; + unsigned long *used_mask; +}; + +/* Generic pmu struct for different pmu types */ +struct hisi_pmu { + const char *name; + struct pmu pmu; + const struct hisi_uncore_ops *ops; + struct hisi_pmu_hwevents pmu_events; + cpumask_t cpus; + struct device *dev; + struct hlist_node node; + u32 scl_id; + u32 ccl_id; + /* Hardware information for different pmu types */ + void __iomem *base; + union { + u32 ddrc_chn_id; + u32 l3c_tag_id; + u32 hha_uid; + }; + int num_counters; + int num_events; + int counter_bits; +}; + +int hisi_uncore_pmu_counter_valid(struct hisi_pmu *hisi_pmu, int idx); +int hisi_uncore_pmu_get_event_idx(struct perf_event *event); +void hisi_uncore_pmu_clear_event_idx(struct hisi_pmu *hisi_pmu, int idx); +void hisi_uncore_pmu_read(struct perf_event *event); +int hisi_uncore_pmu_add(struct perf_event *event, int flags); +void hisi_uncore_pmu_del(struct perf_event *event, int flags); +void hisi_uncore_pmu_start(struct perf_event *event, int flags); +void hisi_uncore_pmu_stop(struct perf_event *event, int flags); +void hisi_uncore_pmu_set_event_period(struct perf_event *event); +u64 hisi_uncore_pmu_event_update(struct perf_event *event); +int hisi_uncore_pmu_event_init(struct perf_event *event); +int hisi_uncore_pmu_setup(struct hisi_pmu *hisi_pmu, const char *pmu_name); +void hisi_uncore_pmu_enable(struct pmu *pmu); +void hisi_uncore_pmu_disable(struct pmu *pmu); +struct hisi_pmu *hisi_pmu_alloc(struct device *dev, u32 num_cntrs); +ssize_t hisi_event_sysfs_show(struct device *dev, + struct device_attribute *attr, char *buf); +ssize_t hisi_format_sysfs_show(struct device *dev, + struct device_attribute *attr, char *buf); +ssize_t hisi_cpumask_sysfs_show(struct device *dev, + struct device_attribute *attr, char *buf); +void hisi_read_scl_and_ccl_id(u32 *scl_id, u32 *ccl_id); +#endif /* __HISI_UNCORE_PMU_H__ */