[RFC V1] perf: qcom: add DDR bandwidth PMU support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Measuring DDR bandwidth allows for an accurate measurement
of memory throughput achieved for a given workload. It also
breaks down the traffic measurement on a per DDR channel basis.

Signed-off-by: Rahul Ramasubramanian <rahulr@xxxxxxxxxxxxxx>
Signed-off-by: Agustin Vega-Frias <agustinv@xxxxxxxxxxxxxx>
---
 drivers/perf/Kconfig                      |  10 +
 drivers/perf/Makefile                     |   1 +
 drivers/perf/qcom_bandwidth_perf_events.c | 970 ++++++++++++++++++++++++++++++
 drivers/perf/qcom_bandwidth_perf_events.h | 156 +++++
 4 files changed, 1137 insertions(+)
 create mode 100644 drivers/perf/qcom_bandwidth_perf_events.c
 create mode 100644 drivers/perf/qcom_bandwidth_perf_events.h

diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
index 08ebaf7..3c9f7e9 100644
--- a/drivers/perf/Kconfig
+++ b/drivers/perf/Kconfig
@@ -87,6 +87,16 @@ config QCOM_L3_PMU
 	   Adds the L3 cache PMU into the perf events subsystem for
 	   monitoring L3 cache events.
 
+config QCOM_BANDWIDTH_PMU
+	bool "Qualcomm Technologies Memory Bandwidth PMU"
+	depends on ARCH_QCOM && ARM64 && ACPI
+	select QCOM_IRQ_COMBINER
+	help
+	   Provides support for the memory bandwidth performance monitor unit (PMU)
+	   in Qualcomm Technologies processors.
+	   Adds the Memory Bandwidth PMU into the perf events subsystem for
+	   monitoring memory bandwidth events.
+
 config XGENE_PMU
         depends on ARCH_XGENE
         bool "APM X-Gene SoC PMU"
diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
index b3902bd..50c20ec 100644
--- a/drivers/perf/Makefile
+++ b/drivers/perf/Makefile
@@ -7,5 +7,6 @@ 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_QCOM_BANDWIDTH_PMU) += qcom_bandwidth_perf_events.o
 obj-$(CONFIG_XGENE_PMU) += xgene_pmu.o
 obj-$(CONFIG_ARM_SPE_PMU) += arm_spe_pmu.o
diff --git a/drivers/perf/qcom_bandwidth_perf_events.c b/drivers/perf/qcom_bandwidth_perf_events.c
new file mode 100644
index 0000000..bcc5667
--- /dev/null
+++ b/drivers/perf/qcom_bandwidth_perf_events.c
@@ -0,0 +1,970 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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.
+ */
+
+/*
+ * This driver adds support for perf events to monitor the DDR
+ * bandwidth in Qualcomm Technologies chips. Each switch in the
+ * interconnect is connected to tthe memory controller and contains a
+ * performace monitoring unit (PMU) that the driver exposes
+ * through the perf events framework.
+ *
+ * The PMU Event Counters
+ * - Event counters, which count occurrences of a configured event.
+ *
+ * These resources are exposed as perf counting events, there is no
+ * support for sampling based on events exposed by the driver. Event
+ * counters are always accumulating.
+ * Events associated with event counters are the following:
+ * ddr-read-bytes: The driver scales the raw pmu count to provide the
+ * number of bytes read from a specific memory controller.
+ *
+ * ddr-write-bytes: The driver scales the raw pmu count to provide the
+ * number of bytes read from a specific memory controller.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/of.h>
+#include <linux/acpi.h>
+#include <linux/perf_event.h>
+#include <linux/platform_device.h>
+#include "qcom_bandwidth_perf_events.h"
+
+
+
+/*
+ * Structures representing a HW PMU and other associated resources
+ */
+
+/*
+ * Represents an event counter
+ *
+ * This type is used to make these operations polymorphic depending on the
+ * type of hardware resources an event uses. The general idea is to associate
+ * a perf_event with a switch_pmu_counter via the index contained in its
+ * hw_perf_event. To accomplish this, an array of switch_pmu_counters is used
+ * and event counters use the  BANDWIDTH_NUM_EVENT_COUNTERS indexes, so the
+ *   event counter index is found by the using the index directly:
+ */
+struct switch_pmu_counter {
+	struct perf_event	*event;
+	/* Called to start event monitoring */
+	void (*start)(struct perf_event *event);
+	/* Called to stop event monitoring (optional) */
+	void (*stop)(struct perf_event *event, int flags);
+	/* Called when the counter overflows (optional) */
+	void (*wrap)(struct perf_event *event);
+	/* Called to update the perf_event */
+	void (*update)(struct perf_event *event);
+};
+
+/*
+ * Represents the hardware PMU
+ *
+ * This type inherits from the core perf events struct pmu and adds data
+ * to manage the PMU resources.
+ */
+struct switch_pmu {
+	/* Base perf pmu */
+	struct pmu	perf_pmu;
+	/* CPU mask exported for user space tools via sysfs */
+	cpumask_t       cpu;
+	/* Node for the hotplug notifier hlist */
+	struct hlist_node     node;
+	/* Register base address */
+	void __iomem	*regs;
+	/* Spinlock used to protect indexed accesses to event counters */
+	raw_spinlock_t	ecsel_lock;
+
+	/* Bitmap to track counter use */
+	unsigned long	used_mask[BITS_TO_LONGS(BANDWIDTH_NUM_TOTAL_COUNTERS)];
+	/* Counter resources */
+	struct switch_pmu_counter counters[BANDWIDTH_NUM_TOTAL_COUNTERS];
+};
+
+#define FIRST_EVENT_COUNTER 0
+
+#define to_switch_pmu(p) (container_of(p, struct switch_pmu, perf_pmu))
+
+static int cpuhp_state_num;
+
+/*
+ * Decoding of settings from perf_event_attr
+ *
+ * Common bits:
+ *
+ * The config format for perf events associated with event counters is:
+ * - config: bits 0-3:event selector, bits 16-22:source selector
+ * - config1: bits 0-21,24-30:filter config, bits 32-45,48-54:filter enable
+ *
+ */
+
+#define PERF_EVENT_ATTR_EXTRACTOR(_name, _config, _size, _shift)	\
+	static inline u32 get_##_name(struct perf_event *event)         \
+	{								\
+		return (event->attr._config >> _shift)                  \
+			& GENMASK(_size - 1, 0);                            \
+	}
+
+PERF_EVENT_ATTR_EXTRACTOR(ec_event_sel, config, 4, 0);
+PERF_EVENT_ATTR_EXTRACTOR(ec_event_lc, config, 1, 32);
+PERF_EVENT_ATTR_EXTRACTOR(ec_source_sel, config, 7, 16);
+
+
+/*
+ * Implementation of global HW PMU operations
+ */
+
+static inline int event_num_counters(struct perf_event *event)
+{
+	return (get_ec_event_lc(event) == 0) ? 1 : 2;
+}
+
+static
+bool switch_pmu__inuse(struct switch_pmu *pmu)
+{
+	/* Check if a given PMU is already in use by IMC */
+	return readl_relaxed(pmu->regs + BANDWIDTH_EC_ENABLE_SET) == 0xF000;
+
+}
+
+static
+void switch_pmu__reset(struct switch_pmu *pmu)
+{
+	u32 all = GENMASK(BANDWIDTH_NUM_EVENT_COUNTERS - 1, 0);
+
+	if (!switch_pmu__inuse(pmu)) {
+		/* Enable access by writing the LAR key */
+		writel_relaxed(BANDWIDTH_LAR_KEY, pmu->regs + BANDWIDTH_LAR);
+
+
+		/* Disable IRQonMSB */
+
+		writel_relaxed(0x0, pmu->regs + BANDWIDTH_EC_IRQ_CONTROL);
+
+		/*
+		 * Assert reset to the EC hardware, use writel to ensure the
+		 * CLEAR commands have been seen by the device before this
+		 * write.
+		 */
+
+		writel(SET(GLOBAL_RESET, 1), pmu->regs +
+			BANDWIDTH_EC_GLOBAL_CONTROL);
+
+		/*
+		 * De-assert reset to the EC hardware, use writel to ensure
+		 *  the reset command has been seen by the device.
+		 */
+
+		writel(SET(GLOBAL_RESET, 0), pmu->regs +
+			BANDWIDTH_EC_GLOBAL_CONTROL);
+		writel(SET(RETRIEVAL_MODE, 1)
+			| SET(GLOBAL_ENABLE, 1) | SET(GLOBAL_TRIGOVRD, 1),
+			pmu->regs + BANDWIDTH_EC_GLOBAL_CONTROL);
+	}
+
+	/* clear the interuppts and event counters */
+	writel_relaxed(all, pmu->regs + BANDWIDTH_EC_ENABLE_CLEAR);
+	writel_relaxed(all, pmu->regs + BANDWIDTH_EC_INTERRUPT_ENABLE_CLEAR);
+};
+
+/*
+ * Event counter operations
+ */
+
+static inline
+void switch_pmu__ec_set_event(struct switch_pmu *pmu, u8 cntr, u32 event)
+{
+
+	writel_relaxed(event, pmu->regs + qcom_bandwidth_ec_source_sel(cntr));
+}
+
+static inline
+void switch_pmu__ec_enable(struct switch_pmu *pmu, u32 cntr)
+{
+	writel_relaxed(SET(ECENSET(cntr), 1), pmu->regs +
+		BANDWIDTH_EC_ENABLE_SET);
+}
+
+static inline
+void switch_pmu__ec_disable(struct switch_pmu *pmu, u32 cntr)
+{
+	writel_relaxed(SET(ECENSET(cntr), 1),
+		    pmu->regs + BANDWIDTH_EC_ENABLE_CLEAR);
+}
+
+static inline
+void switch_pmu__ec_enable_interrupt(struct switch_pmu *pmu, u32 cntr)
+{
+	u32 val = readl_relaxed(pmu->regs + BANDWIDTH_EC_IRQ_CONTROL);
+
+	writel_relaxed(val | BIT(cntr), pmu->regs + BANDWIDTH_EC_IRQ_CONTROL);
+	writel_relaxed(SET(ECINTENCLR(cntr), 1),
+		    pmu->regs + BANDWIDTH_EC_INTERRUPT_ENABLE_SET);
+}
+
+static inline
+void switch_pmu__ec_disable_interrupt(struct switch_pmu *pmu, u32 cntr)
+{
+	u32 val = readl_relaxed(pmu->regs + BANDWIDTH_EC_IRQ_CONTROL);
+
+	writel(val & ~BIT(cntr), pmu->regs + BANDWIDTH_EC_IRQ_CONTROL);
+	writel(SET(ECINTENCLR(cntr), 1),
+		    pmu->regs + BANDWIDTH_EC_INTERRUPT_ENABLE_CLEAR);
+}
+
+static inline
+u32 switch_pmu__ec_read_ovsr(struct switch_pmu *pmu)
+{
+	return readl_relaxed(pmu->regs + BANDWIDTH_EC_OVF_STATUS);
+}
+
+static inline
+void switch_pmu__ec_write_ovsr(struct switch_pmu *pmu, u32 value)
+{
+	writel_relaxed(value, pmu->regs + BANDWIDTH_EC_OVF_STATUS);
+}
+
+static inline
+bool switch_pmu__any_event_counter_overflowed(u32 ovsr)
+{
+	return (ovsr & GENMASK(BANDWIDTH_NUM_EVENT_COUNTERS - 1, 0)) != 0;
+}
+
+static inline
+int switch_pmu__ec_has_overflowed(u32 ovsr, u8 cntr)
+{
+	return GET(ECOVF(cntr), ovsr) != 0;
+}
+
+static inline
+void switch_pmu__ec_set_value(struct switch_pmu *pmu, u8 cntr, u32 value)
+{
+	unsigned long flags;
+	bool reenable = false;
+
+	/*
+	 * Quirk: The counter needs to be disabled before updating.
+	 */
+	if ((readl_relaxed(pmu->regs + BANDWIDTH_EC_ENABLE_SET) &
+		    SET(ECENSET(cntr), 1)) != 0) {
+		switch_pmu__ec_disable(pmu, cntr);
+		reenable = true;
+	}
+
+	raw_spin_lock_irqsave(&pmu->ecsel_lock, flags);
+	writel_relaxed(SET(ECSEL, cntr), pmu->regs + BANDWIDTH_EC_COUNTER_SEL);
+
+	/*
+	 * Use writel because the write to BANDWIDTH_EC_COUNTER_SEL needs
+	 * to be observed before the write to BANDWIDTH_EC_COUNT.
+	 */
+
+	writel(value, pmu->regs + BANDWIDTH_EC_COUNT);
+	raw_spin_unlock_irqrestore(&pmu->ecsel_lock, flags);
+
+	if (reenable)
+		switch_pmu__ec_enable(pmu, cntr);
+}
+
+static inline
+u32 switch_pmu__ec_get_value(struct switch_pmu *pmu, u8 cntr)
+{
+	u32 result;
+	u32 sel;
+	unsigned long flags;
+	unsigned long num_attempts = 0;
+
+	do {
+		raw_spin_lock_irqsave(&pmu->ecsel_lock, flags);
+		writel_relaxed(SET(ECSEL, cntr), pmu->regs +
+			BANDWIDTH_EC_COUNTER_SEL);
+
+		/*
+		 * The write to BANDWIDTH_EC_COUNTER_SEL needs to be observed
+		 * before the read to BANDWIDTH_EC_COUNT.
+		 */
+		mb();
+
+		result = readl_relaxed(pmu->regs + BANDWIDTH_EC_COUNT);
+		raw_spin_unlock_irqrestore(&pmu->ecsel_lock, flags);
+		num_attempts++;
+		sel = readl_relaxed(pmu->regs + BANDWIDTH_EC_COUNTER_SEL);
+	} while ((sel != SET(ECSEL, cntr))
+		&& (num_attempts <= DDRBW_MAX_RETRIES));
+
+	/* Exit gracefully to avoid freeze */
+	if (num_attempts >= DDRBW_MAX_RETRIES)
+		return DDR_BW_READ_FAIL;
+
+	return result;
+}
+
+static inline
+bool switch_pmu__any_event_counter_active(struct switch_pmu *pmu)
+{
+	int idx = find_next_bit(pmu->used_mask, BANDWIDTH_NUM_TOTAL_COUNTERS,
+		    FIRST_EVENT_COUNTER);
+
+	return idx != BANDWIDTH_NUM_TOTAL_COUNTERS;
+}
+
+/*
+ * Event counter switch_pmu_counter method implementation.
+ */
+
+static
+void switch_pmu__32bit_event_counter_update(struct perf_event *event)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	u32 ec_idx = event->hw.idx - FIRST_EVENT_COUNTER;
+	u32 delta, prev, now;
+
+	do {
+		prev = (u32)local64_read(&event->hw.prev_count);
+		now = switch_pmu__ec_get_value(pmu, ec_idx);
+	} while (local64_cmpxchg(&event->hw.prev_count, prev, now) != prev);
+
+	delta = now - prev;
+	local64_add(delta, &event->count);
+}
+
+static
+void switch_pmu__64bit_event_counter_update(struct perf_event *event)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	int idx = event->hw.idx - FIRST_EVENT_COUNTER;
+	u32 hi, lo;
+	u64 prev, now;
+
+	do {
+		prev = local64_read(&event->hw.prev_count);
+		do {
+			hi = switch_pmu__ec_get_value(pmu, idx + 1);
+			lo = switch_pmu__ec_get_value(pmu, idx);
+		} while (hi != switch_pmu__ec_get_value(pmu, idx + 1));
+		now = ((u64)hi << 32) | lo;
+	} while (local64_cmpxchg(&event->hw.prev_count, prev, now) != prev);
+
+	local64_add(now - prev, &event->count);
+}
+
+static
+void switch_pmu__event_counter_program(struct perf_event *event)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+
+	u32 ec_idx = event->hw.idx - FIRST_EVENT_COUNTER;
+	u32 ev_type = SET(ECSOURCESEL, get_ec_source_sel(event)) |
+		    SET(ECEVENTSEL, get_ec_event_sel(event));
+
+	event->hw.state = 0;
+
+	local64_set(&event->hw.prev_count, 0);
+	switch_pmu__ec_set_value(pmu, ec_idx, 0);
+	switch_pmu__ec_set_event(pmu, ec_idx, ev_type);
+}
+
+static
+void enable_64bit_ganging(struct perf_event *event, u32 idx)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+
+	/* according to errata doc, this needs to be done
+	 * for the odd counter
+	 */
+	u16 gang_regs;
+	u32 ev_type = SET(ECSOURCESEL, 0x0) | SET(ECEVENTSEL, 0xf);
+
+	switch_pmu__ec_set_event(pmu, idx, ev_type);
+
+	/* enable ganging RMW */
+	gang_regs = readl_relaxed(pmu->regs + BANDWIDTH_EC_GANG);
+	gang_regs |= BIT(idx);
+	writel_relaxed(gang_regs, pmu->regs + BANDWIDTH_EC_GANG);
+
+}
+
+static
+void disable_64bit_ganging(struct perf_event *event, u32 idx)
+{
+	u16 gang_regs;
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+
+	gang_regs = readl_relaxed(pmu->regs + BANDWIDTH_EC_GANG);
+	gang_regs = gang_regs & ~BIT(idx);
+	writel_relaxed(gang_regs, pmu->regs + BANDWIDTH_EC_GANG);
+
+}
+static
+void switch_pmu_event_32bit_counter_start(struct perf_event *event)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	u32 ec_idx = event->hw.idx - FIRST_EVENT_COUNTER;
+
+	switch_pmu__event_counter_program(pmu->counters[ec_idx].event);
+	switch_pmu__ec_enable_interrupt(pmu, ec_idx);
+	switch_pmu__ec_enable(pmu, ec_idx);
+}
+
+static
+void switch_pmu_event_64bit_counter_start(struct perf_event *event)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	u32 ec_idx = event->hw.idx - FIRST_EVENT_COUNTER;
+
+	switch_pmu__event_counter_program(pmu->counters[ec_idx].event);
+	enable_64bit_ganging(event, ec_idx + 1);
+	switch_pmu__ec_enable(pmu, ec_idx);
+	switch_pmu__ec_enable(pmu, ec_idx + 1);
+}
+
+static
+void switch_pmu_event_32bit_counter_stop(struct perf_event *event, int flags)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	u32 ec_idx = event->hw.idx - FIRST_EVENT_COUNTER;
+
+	switch_pmu__ec_disable_interrupt(pmu, ec_idx);
+	switch_pmu__ec_disable(pmu, ec_idx);
+}
+
+static
+void switch_pmu_event_64bit_counter_stop(struct perf_event *event, int flags)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	u32 ec_idx = event->hw.idx - FIRST_EVENT_COUNTER;
+
+	switch_pmu__ec_disable_interrupt(pmu, ec_idx);
+	switch_pmu__ec_disable(pmu, ec_idx);
+	switch_pmu__ec_disable_interrupt(pmu, ec_idx + 1);
+	switch_pmu__ec_disable(pmu, ec_idx + 1);
+	disable_64bit_ganging(event, ec_idx + 1);
+}
+
+static
+void switch_pmu_event_32bit_counter_wrap(struct perf_event *event)
+{
+	switch_pmu__32bit_event_counter_update(event);
+}
+
+/*
+ * Core abstract PMU functions and management of the software counters.
+ */
+
+static
+void switch_pmu__nop(struct pmu *perf_pmu)
+{
+}
+
+static
+int switch_pmu__reserve_event_counter(struct switch_pmu *pmu,
+		    struct perf_event *event, int sz)
+{
+	int idx;
+
+	idx = bitmap_find_free_region(pmu->used_mask,
+	    BANDWIDTH_NUM_TOTAL_COUNTERS, sz);
+	if (idx < 0)
+		return -EAGAIN;
+	return idx;
+}
+
+/*
+ * We must NOT create groups containing events from multiple hardware PMUs,
+ * although mixing different software and hardware PMUs is allowed.
+ */
+static bool switch_pmu__validate_event_group(struct perf_event *event)
+{
+	struct perf_event *leader = event->group_leader;
+	struct perf_event *sibling;
+	int counters = 0;
+
+	if (leader->pmu != event->pmu && !is_software_event(leader))
+		return false;
+
+	counters = event_num_counters(event);
+	counters += event_num_counters(leader);
+
+	for_each_sibling_event(sibling, leader) {
+		if (is_software_event(sibling))
+			continue;
+		if (sibling->pmu != event->pmu)
+			return false;
+		counters += event_num_counters(sibling);
+	}
+
+	/*
+	 * If the group requires more counters than the HW has, it
+	 * cannot ever be scheduled.
+	 */
+	return counters <= BANDWIDTH_NUM_TOTAL_COUNTERS;
+}
+static
+int switch_pmu__event_init(struct perf_event *event)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	struct hw_perf_event *hwc = &event->hw;
+
+	/*
+	 * Is the event for this PMU?
+	 */
+	if (event->attr.type != event->pmu->type)
+		return -ENOENT;
+
+	/*
+	 * We cannot filter accurately so we just don't allow it at all.
+	 */
+	if (event->attr.exclude_user || event->attr.exclude_kernel ||
+		    event->attr.exclude_hv || event->attr.exclude_idle)
+		return -EINVAL;
+
+	hwc->idx = -1;
+
+	/* Sampling not supported these are system counters/events */
+	if (hwc->sample_period)
+		return -EINVAL;
+
+	/*
+	 * Task mode not available, these are system counters not attributable
+	 * to any CPU and therefore cannot attribute per-task.
+	 */
+	if (event->cpu < 0)
+		return -EINVAL;
+	/* set event cpu to the cpumask*/
+	event->cpu = cpumask_first(&pmu->cpu);
+
+	/* Validate the group */
+	if (!switch_pmu__validate_event_group(event))
+		return -EINVAL;
+
+	return 0;
+}
+
+static
+int switch_pmu__event_add(struct perf_event *event, int flags)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	struct hw_perf_event *hwc = &event->hw;
+	int idx;
+	int err = 0;
+	int sz;
+
+	sz = get_ec_event_lc(event);
+
+	/* Try to find a hardware resource for this event */
+	idx = switch_pmu__reserve_event_counter(pmu, event, sz);
+	if (idx < 0) {
+		err = idx;
+		goto out;
+	}
+
+	hwc->idx = idx;
+	hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
+
+	if (sz == 0) {
+		pmu->counters[idx] = (struct switch_pmu_counter) {
+			.event = event,
+			.start = switch_pmu_event_32bit_counter_start,
+			.stop = switch_pmu_event_32bit_counter_stop,
+			.wrap = switch_pmu_event_32bit_counter_wrap,
+			.update = switch_pmu__32bit_event_counter_update,
+		};
+
+	} else {
+		pmu->counters[idx] = (struct switch_pmu_counter) {
+			.event = event,
+			.start = switch_pmu_event_64bit_counter_start,
+			.stop = switch_pmu_event_64bit_counter_stop,
+			.update = switch_pmu__64bit_event_counter_update,
+			.wrap = NULL
+		};
+		pmu->counters[idx + 1] = pmu->counters[idx];
+	}
+
+	if (flags & PERF_EF_START)
+		pmu->counters[idx].start(pmu->counters[idx].event);
+
+out:
+	return err;
+}
+
+static
+void switch_pmu__event_start(struct perf_event *event, int flags)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	struct hw_perf_event *hwc = &event->hw;
+
+	pmu->counters[hwc->idx].start(pmu->counters[hwc->idx].event);
+}
+
+static
+void switch_pmu__event_stop(struct perf_event *event, int flags)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	struct hw_perf_event *hwc = &event->hw;
+	struct switch_pmu_counter *c = &pmu->counters[hwc->idx];
+
+
+	if (!(hwc->state & PERF_HES_STOPPED)) {
+		if (c->stop)
+			c->stop(c->event, flags);
+
+		if (flags & PERF_EF_UPDATE)
+			c->update(c->event);
+		hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
+	}
+}
+
+static
+void switch_pmu__event_del(struct perf_event *event, int flags)
+{
+
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	struct hw_perf_event *hwc = &event->hw;
+	struct switch_pmu_counter *c = &pmu->counters[hwc->idx];
+	struct switch_pmu_counter *cl = &pmu->counters[hwc->idx + 1];
+	int sz;
+
+	sz = get_ec_event_lc(event);
+
+	if (c->stop)
+		c->stop(c->event, flags | PERF_EF_UPDATE);
+	c->update(c->event);
+	c->event = NULL;
+	bitmap_release_region(pmu->used_mask, hwc->idx, sz);
+
+	/* Null set the upper counter when the long counter was enabled*/
+	if (sz)
+		cl->event = NULL;
+}
+
+
+static
+void switch_pmu__event_read(struct perf_event *event)
+{
+	struct switch_pmu *pmu = to_switch_pmu(event->pmu);
+	struct hw_perf_event *hwc = &event->hw;
+
+	pmu->counters[hwc->idx].update(pmu->counters[hwc->idx].event);
+}
+
+static
+int dummy_event_idx(struct perf_event *event)
+{
+	return 0;
+}
+
+static
+bool switch_pmu__ec_handle_irq(struct switch_pmu *pmu)
+{
+	bool handled = false;
+	u32 ovs = switch_pmu__ec_read_ovsr(pmu);
+	int idx;
+
+	switch_pmu__ec_write_ovsr(pmu, ovs);
+
+	if (!switch_pmu__any_event_counter_overflowed(ovs))
+		return handled;
+
+	for (idx = 0; idx < BANDWIDTH_NUM_EVENT_COUNTERS; ++idx) {
+		struct switch_pmu_counter *counter;
+
+		if (!switch_pmu__ec_has_overflowed(ovs, idx))
+			continue;
+		counter = &pmu->counters[idx + FIRST_EVENT_COUNTER];
+		if (!counter->event)
+			continue;
+		counter->wrap(counter->event);
+		handled = true;
+	}
+
+	return handled;
+}
+
+
+static
+irqreturn_t switch_pmu__handle_irq(int irq_num, void *data)
+{
+	bool handled = false;
+	struct switch_pmu *pmu = data;
+
+	if (switch_pmu__any_event_counter_active(pmu))
+		handled = switch_pmu__ec_handle_irq(pmu);
+
+	/*
+	 * Handle the pending perf events.
+	 *
+	 * Note: this call *must* be run with interrupts disabled. For
+	 * platforms that can have the PMU interrupts raised as an NMI, this
+	 * will not work.
+	 */
+
+	irq_work_run();
+
+	return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+/*
+ * Fixed attribute groups exposed for perf in the format group.
+ *
+ * The config format for perf events associated with event counters is:
+ * - config: bits 0-3:event selector, bits 16-22:source selector
+ * - config1: bits 0-21,24-30:filter config, bits 32-45,48-54:filter enable
+ *
+ */
+
+/* Event counters */
+
+#define DDRBW_ATTR(_name, _str)					\
+	(&((struct perf_pmu_events_attr[]){				\
+	   {.attr = __ATTR(_name, 0444, perf_event_sysfs_show, NULL),\
+	.id = 0,						\
+	.event_str = _str }					\
+	})[0].attr.attr)
+
+
+static struct attribute *qcom_bandwidth_pmu_formats[] = {
+	DDRBW_ATTR(ecsourcesel, "config:16-22"),
+	DDRBW_ATTR(eceventsel, "config:0-3"),
+	DDRBW_ATTR(lc, "config:32"),
+	NULL,
+};
+
+static struct attribute_group qcom_bandwidth_pmu_format_group = {
+	.name = "format",
+	.attrs = qcom_bandwidth_pmu_formats,
+};
+
+static ssize_t qcom_bandwidth_pmu_cpumask_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct switch_pmu *pmu = to_switch_pmu(dev_get_drvdata(dev));
+
+	return cpumap_print_to_pagebuf(true, buf, &pmu->cpu);
+}
+
+static struct device_attribute qcom_bandwidth_pmu_cpumask_attr =
+	__ATTR(cpumask, 0444, qcom_bandwidth_pmu_cpumask_show, NULL);
+
+static struct attribute *qcom_bandwidth_pmu_cpumask_attrs[] = {
+	&qcom_bandwidth_pmu_cpumask_attr.attr,
+	NULL,
+};
+
+static struct attribute_group qcom_bandwidth_pmu_cpumask_attr_group = {
+	.attrs = qcom_bandwidth_pmu_cpumask_attrs,
+};
+
+
+static struct attribute *qcom_ddrbw_pmu_events[] = {
+	DDRBW_ATTR(ddr-read-beats, "ecsourcesel=0x14, eceventsel=0"),
+	DDRBW_ATTR(ddr-read-beats.unit, "Bytes"),
+	DDRBW_ATTR(ddr-read-beats.scale, "32"),
+	DDRBW_ATTR(ddr-write-beats, "ecsourcesel=0x15, eceventsel=0"),
+	DDRBW_ATTR(ddr-write-beats.unit, "Bytes"),
+	DDRBW_ATTR(ddr-write-beats.scale, "32"),
+	NULL
+};
+
+static struct attribute_group qcom_bandwidth_pmu_events_group = {
+	.name = "events",
+	.attrs = qcom_ddrbw_pmu_events,
+};
+
+static const struct attribute_group **init_attribute_groups(void)
+{
+	static const struct attribute_group *result[4];
+
+	result[0] = &qcom_bandwidth_pmu_format_group;
+	result[1] = &qcom_bandwidth_pmu_cpumask_attr_group;
+	result[2] = &qcom_bandwidth_pmu_events_group;
+	result[3] = NULL;
+	return result;
+}
+
+static const struct attribute_group **attr_groups;
+
+/*
+ * Device probing and initialization.
+ */
+
+static int qcom_bandwidth_pmu_offline_cpu(unsigned int cpu,
+	struct hlist_node *node)
+{
+	struct switch_pmu *pmu = hlist_entry_safe(node,
+		struct switch_pmu, node);
+	unsigned int target;
+
+	if (!cpumask_test_and_clear_cpu(cpu, &pmu->cpu))
+		return 0;
+	target = cpumask_any_but(cpu_online_mask, cpu);
+	if (target >= nr_cpu_ids)
+		return 0;
+	perf_pmu_migrate_context(&pmu->perf_pmu, cpu, target);
+	cpumask_set_cpu(target, &pmu->cpu);
+	return 0;
+}
+
+static const struct acpi_device_id qcom_bandwidth_pmu_acpi_match[] = {
+	{ "QCOM80C1", },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(acpi, qcom_bandwidth_pmu_acpi_match);
+
+static int qcom_bandwidth_pmu_probe(struct platform_device *pdev)
+{
+	int result, irq, err;
+	struct resource *regs_rc;
+	struct switch_pmu *pmu;
+	unsigned long uid;
+	struct acpi_device *device;
+	char *name;
+
+	regs_rc = platform_get_resource(pdev, IORESOURCE_MEM, RES_BW);
+
+	name = devm_kzalloc(&pdev->dev, DDRBW_PMU_NAME_LEN, GFP_KERNEL);
+	if (!name)
+		return -ENOMEM;
+
+	pmu = devm_kzalloc(&pdev->dev, sizeof(*pmu), GFP_KERNEL);
+	if (!pmu)
+		return -ENOMEM;
+
+	*pmu = (struct switch_pmu) {
+		.perf_pmu = {
+			/* Tag this as a SW context to disable multiplexing */
+			.task_ctx_nr	= perf_invalid_context,
+
+			.pmu_enable	= switch_pmu__nop,
+			.pmu_disable	= switch_pmu__nop,
+			.event_init	= switch_pmu__event_init,
+			.add		= switch_pmu__event_add,
+			.del		= switch_pmu__event_del,
+			.start		= switch_pmu__event_start,
+			.stop		= switch_pmu__event_stop,
+			.read		= switch_pmu__event_read,
+
+			.event_idx	= dummy_event_idx,
+
+			.attr_groups	= attr_groups
+		},
+		.counters = {
+			[0 ... BANDWIDTH_NUM_TOTAL_COUNTERS - 1] {}
+		}
+	};
+
+	raw_spin_lock_init(&pmu->ecsel_lock);
+
+	pmu->regs = devm_ioremap_resource(&pdev->dev, regs_rc);
+	if (IS_ERR(pmu->regs)) {
+		dev_err(&pdev->dev, "Can't map regs @%pa!\n",
+		    &regs_rc->start);
+		return PTR_ERR(pmu->regs);
+	}
+
+	irq = platform_get_irq(pdev, IRQ_BW);
+	if (irq <= 0) {
+		dev_err(&pdev->dev, "Failed to get valid irq\n");
+		return -ENODEV;
+	}
+
+	if (acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &device))
+		return -ENODEV;
+
+	if (kstrtol(device->pnp.unique_id, 10, &uid) < 0) {
+		dev_err(&pdev->dev, "unable to read ACPI uid\n");
+		return -ENODEV;
+	}
+
+	snprintf(name, DDRBW_PMU_NAME_LEN, DDRBW_PMU_NAME_FORMAT, uid);
+	pmu->perf_pmu.name = name;
+
+	err = devm_request_irq(&pdev->dev, irq, switch_pmu__handle_irq,
+		    IRQF_NOBALANCING, pmu->perf_pmu.name, pmu);
+	if (err) {
+		dev_err(&pdev->dev, "Unable to request IRQ%d\n", irq);
+		return err;
+	}
+
+	/* Designate the probing CPU as the context for the PMU */
+	cpumask_set_cpu(smp_processor_id(), &pmu->cpu);
+
+	switch_pmu__reset(pmu);
+	result = perf_pmu_register(&pmu->perf_pmu, pmu->perf_pmu.name, -1);
+
+	if (result < 0) {
+		dev_err(&pdev->dev, "Failed to register(%d)\n", result);
+		return result;
+	}
+	dev_info(&pdev->dev, "Registered %s PMU, type: %d\n",
+		pmu->perf_pmu.name, pmu->perf_pmu.type);
+
+	/* Add this instance to the list used by the offline callback */
+	cpuhp_state_add_instance_nocalls(cpuhp_state_num, &pmu->node);
+
+	platform_set_drvdata(pdev, pmu);
+
+	return result;
+}
+
+static int qcom_bandwidth_pmu_remove(struct platform_device *pdev)
+{
+	struct switch_pmu *pmu = platform_get_drvdata(pdev);
+
+	cpuhp_state_remove_instance_nocalls(cpuhp_state_num, &pmu->node);
+	perf_pmu_unregister(&pmu->perf_pmu);
+	return 0;
+}
+
+static struct platform_driver qcom_bandwidth_pmu_driver = {
+	.driver = {
+		.name = "qcom-bandwidth-pmu-v1",
+		.owner = THIS_MODULE,
+		.acpi_match_table = ACPI_PTR(qcom_bandwidth_pmu_acpi_match),
+	},
+	.probe = qcom_bandwidth_pmu_probe,
+	.remove = qcom_bandwidth_pmu_remove,
+};
+
+static int __init register_qcom_bandwidth_pmu_driver(void)
+{
+	if (attr_groups == NULL)
+		attr_groups = init_attribute_groups();
+
+	/* Install a hook to update the context CPU in case it goes offline */
+	cpuhp_state_num = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
+		"perf/qcom/msw:online", NULL, qcom_bandwidth_pmu_offline_cpu);
+	if (cpuhp_state_num < 0)
+		return cpuhp_state_num;
+
+	return platform_driver_register(&qcom_bandwidth_pmu_driver);
+}
+
+static void __exit unregister_qcom_bandwidth_pmu_driver(void)
+{
+	cpuhp_remove_multi_state(cpuhp_state_num);
+	platform_driver_unregister(&qcom_bandwidth_pmu_driver);
+}
+
+module_init(register_qcom_bandwidth_pmu_driver);
+module_exit(unregister_qcom_bandwidth_pmu_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/perf/qcom_bandwidth_perf_events.h b/drivers/perf/qcom_bandwidth_perf_events.h
new file mode 100644
index 0000000..120a10b
--- /dev/null
+++ b/drivers/perf/qcom_bandwidth_perf_events.h
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef _QCOM_BANDWIDTH_PERF_EVENTS_H_
+#define _QCOM_BANDWIDTH_PERF_EVENTS_H_
+
+#include<linux/bitops.h>
+
+/*
+ * General constants
+ */
+
+
+#define BANDWIDTH_NUM_EVENT_COUNTERS    12
+#define BANDWIDTH_NUM_TOTAL_COUNTERS    BANDWIDTH_NUM_EVENT_COUNTERS
+#define BANDWIDTH_LAR_KEY               0xC5ACCE55
+
+/*
+ * Register offsets
+ */
+
+/* ID and Coresight registers */
+
+#define BANDWIDTH_LAR           0xFB0
+
+
+/* Event counter registers */
+
+/*
+ * Because of interleaving, some gaps in the map exists
+ * (7th bit cannot be used).
+ * To accommodate this mapping,
+ * we have different offsets for different sets of counters.
+ */
+
+
+static inline u32 qcom_bandwidth_ec_source_sel(u8 __cntr)
+{
+	if (__cntr >= 0 && __cntr <= 2)
+		return (0x240 + ((__cntr) & 0xF) * 24);
+	else if (__cntr >= 3 && __cntr <= 7)
+		return (0x2C0 + ((__cntr) & 0xF) * 24);
+	else if (__cntr >= 8 && __cntr <= 13)
+		return (0x340 + ((__cntr) & 0xF) * 24);
+	else
+		return (0x3C0 + ((__cntr) & 0xF) * 24);
+}
+
+
+#define BANDWIDTH_EC_GLOBAL_CONTROL             0xA00
+#define BANDWIDTH_EC_ENABLE_SET                 0xA10
+#define BANDWIDTH_EC_ENABLE_CLEAR               0xA18
+#define BANDWIDTH_EC_INTERRUPT_ENABLE_SET       0xA20
+#define BANDWIDTH_EC_INTERRUPT_ENABLE_CLEAR     0xA28
+#define BANDWIDTH_EC_TRIGGER_THRESHOLD_LO       0xA30
+#define BANDWIDTH_EC_TRIGGER_THRESHOLD_HI       0xC30
+#define BANDWIDTH_EC_GANG                       0xE30
+#define BANDWIDTH_EC_GANG_CONFIG0               0xE38
+#define BANDWIDTH_EC_GANG_CONFIG1               0xE40
+#define BANDWIDTH_EC_GANG_CONFIG2               0xE48
+#define BANDWIDTH_EC_OVF_STATUS                 0xF00
+#define BANDWIDTH_EC_COUNTER_SEL                0xF08
+#define BANDWIDTH_EC_COUNT                      0xF10
+#define BANDWIDTH_EC_SWINC                      0x1320
+#define BANDWIDTH_EC_IRQ_CONTROL                0x1358
+
+/* IRQ/resource position in ACPI */
+#define IRQ_BW                  2
+#define RES_BW                  4
+#define DDRBW_PMU_NAME_FORMAT   "bwddr_0_%ld"
+#define DDRBW_PMU_NAME_LEN      11
+#define DDRBW_MAX_RETRIES       3
+#define DDR_BW_READ_FAIL        0
+/*
+ * Bit field definitions, defined as (<size>, <shift>)
+ * Please note that fields that take up the whole register
+ * are not included here, as those can be set/read directly.
+ */
+
+/* BANDWIDTH_EC_SOURCE_SEL */
+#define ECSOURCESEL                     (7, 16)
+#define ECEVENTSEL                      (4,  0)
+
+
+
+/* BANDWIDTH_EC_GLOBAL_CONTROL/MONACO_TC_GLOBAL_CONTROL */
+
+#define GLOBAL_TRIGOVRD                 (1, 4)
+#define CAPTURE                         (1, 3)
+#define RETRIEVAL_MODE                  (1, 2)
+#define GLOBAL_RESET                    (1, 1)
+#define GLOBAL_ENABLE                   (1, 0)
+
+/* MONACO_EC_ROLLOVER_CONTROL */
+
+#define ECSATURATEEN(__cntr)            (1, ((__cntr) & 0xF))
+
+/* MONACO_EC_ENABLE_SET */
+
+#define ECENSET(__cntr)                 (1, ((__cntr) & 0xF))
+
+/* MONACO_EC_ENABLE_CLEAR */
+
+#define ECENCLEAR(__cntr)               (1, ((__cntr) & 0xF))
+
+/* MONACO_EC_INTERRUPT_ENABLE_SET */
+
+#define ECINTENSET(__cntr)              (1, ((__cntr) & 0xF))
+
+/* MONACO_EC_INTERRUPT_ENABLE_CLEAR */
+
+#define ECINTENCLR(__cntr)              (1, ((__cntr) & 0xF))
+
+/* MONACO_EC_GANG */
+
+#define ECGANGEN(__pair)                (1, (((__pair) & 0x7) * 2 + 1))
+
+/* MONACO_EC_OVF_STATUS */
+
+#define ECOVF(__cntr)                   (1, ((__cntr) & 0xF))
+
+/* MONACO_EC_COUNTER_SEL */
+
+#define ECSEL                           (4, 0)
+
+/* MONACO_EC_SWINC */
+
+#define ECSWINC(__cntr)                 (1, ((__cntr) & 0xF))
+
+
+/* MONACO_LSR */
+
+#define NTT                             (1,  2)
+#define SLK                             (1,  1)
+#define SLI                             (1,  0)
+
+/*
+ * Bit field manipulation macros.
+ * These use the bitfield definitions above to set or get the given field.
+ */
+
+#define __SIZE(__sz, __sh) __sz
+#define __SHIFT(__sz, __sh) __sh
+#define __SETI(__b, __s, __v) ((u32)(((__v) & GENMASK((__b - 1), 0)) << (__s)))
+#define __CLRI(__b, __s, __v) ((u32)((__v) & ~(GENMASK((__b - 1) + __s, __s))))
+#define __GETI(__b, __s, __v) ((u32)(((__v) >> (__s)) & GENMASK((__b - 1), 0)))
+
+/* Return a value with the given bitfield set to the given value */
+#define SET(__f, __v) __SETI(__SIZE __f, __SHIFT __f, (__v))
+
+/* Return a value with the given bitfield set to zero */
+#define CLR(__f, __v) __CLRI(__SIZE __f, __SHIFT __f, (__v))
+
+/* Retrieve the given bitfield from the given value */
+#define GET(__f, __v) __GETI(__SIZE __f, __SHIFT __f, (__v))
+
+#endif
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux IBM ACPI]     [Linux Power Management]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux