aboutsummaryrefslogtreecommitdiff
path: root/arch
diff options
context:
space:
mode:
Diffstat (limited to 'arch')
-rw-r--r--arch/arm/include/asm/pmu.h10
-rw-r--r--arch/arm/kernel/perf_event.c11
-rw-r--r--arch/arm/kernel/perf_event_cpu.c94
-rw-r--r--arch/arm/mach-ux500/cpu-db8500.c29
4 files changed, 107 insertions, 37 deletions
diff --git a/arch/arm/include/asm/pmu.h b/arch/arm/include/asm/pmu.h
index 0b648c541293..36472c3cc283 100644
--- a/arch/arm/include/asm/pmu.h
+++ b/arch/arm/include/asm/pmu.h
@@ -81,6 +81,12 @@ struct pmu_hw_events {
raw_spinlock_t pmu_lock;
};
+struct arm_pmu_work {
+ struct irq_work work;
+ struct arm_pmu *arm_pmu;
+ atomic_t ret;
+};
+
struct arm_pmu {
struct pmu pmu;
cpumask_t active_irqs;
@@ -101,6 +107,7 @@ struct arm_pmu {
void (*reset)(void *);
int (*request_irq)(struct arm_pmu *, irq_handler_t handler);
void (*free_irq)(struct arm_pmu *);
+ irqreturn_t (*handle_irq_none)(struct arm_pmu *);
int (*map_event)(struct perf_event *event);
int num_events;
atomic_t active_events;
@@ -108,6 +115,9 @@ struct arm_pmu {
u64 max_period;
struct platform_device *plat_device;
struct pmu_hw_events *(*get_hw_events)(void);
+ int single_irq;
+ struct arm_pmu_work __percpu *work;
+ atomic_t remaining_work;
};
#define to_arm_pmu(p) (container_of(p, struct arm_pmu, pmu))
diff --git a/arch/arm/kernel/perf_event.c b/arch/arm/kernel/perf_event.c
index b50a770f8c99..48eaae5427f9 100644
--- a/arch/arm/kernel/perf_event.c
+++ b/arch/arm/kernel/perf_event.c
@@ -306,22 +306,17 @@ validate_group(struct perf_event *event)
static irqreturn_t armpmu_dispatch_irq(int irq, void *dev)
{
struct arm_pmu *armpmu;
- struct platform_device *plat_device;
- struct arm_pmu_platdata *plat;
int ret;
u64 start_clock, finish_clock;
if (irq_is_percpu(irq))
dev = *(void **)dev;
armpmu = dev;
- plat_device = armpmu->plat_device;
- plat = dev_get_platdata(&plat_device->dev);
start_clock = sched_clock();
- if (plat && plat->handle_irq)
- ret = plat->handle_irq(irq, dev, armpmu->handle_irq);
- else
- ret = armpmu->handle_irq(irq, dev);
+ ret = armpmu->handle_irq(irq, dev);
+ if (unlikely(ret == IRQ_NONE) && armpmu->handle_irq_none)
+ ret = armpmu->handle_irq_none(dev);
finish_clock = sched_clock();
perf_sample_event_took(finish_clock - start_clock);
diff --git a/arch/arm/kernel/perf_event_cpu.c b/arch/arm/kernel/perf_event_cpu.c
index eb2c4d55666b..e7153dc3b489 100644
--- a/arch/arm/kernel/perf_event_cpu.c
+++ b/arch/arm/kernel/perf_event_cpu.c
@@ -88,6 +88,75 @@ static void cpu_pmu_disable_percpu_irq(void *data)
disable_percpu_irq(irq);
}
+/*
+ * Workaround logic that is distributed to all cores if the PMU has only
+ * a single IRQ and the CPU receiving that IRQ cannot handle it. Its
+ * job is to try to service the interrupt on the current CPU. It will
+ * also enable the IRQ again if all the other CPUs have already tried to
+ * service it.
+ */
+static void cpu_pmu_do_percpu_work(struct irq_work *w)
+{
+ struct arm_pmu_work *work = container_of(w, struct arm_pmu_work, work);
+ struct arm_pmu *cpu_pmu = work->arm_pmu;
+
+ atomic_set(&work->ret,
+ cpu_pmu->handle_irq(cpu_pmu->single_irq, cpu_pmu));
+
+ if (atomic_dec_and_test(&cpu_pmu->remaining_work))
+ enable_irq(cpu_pmu->single_irq);
+}
+
+/*
+ * This callback, which is enabled only on SMP platforms that are
+ * running with a single IRQ, is called when the PMU handler running in
+ * the current CPU cannot service the interrupt.
+ *
+ * It will disable the interrupt and distribute irqwork to all other
+ * processors in the system. Hopefully one of them will clear the
+ * interrupt...
+ */
+static irqreturn_t cpu_pmu_handle_irq_none(struct arm_pmu *cpu_pmu)
+{
+ int num_online = num_online_cpus();
+ irqreturn_t ret = IRQ_NONE;
+ int cpu, cret;
+
+ if (num_online <= 1)
+ return IRQ_NONE;
+
+ disable_irq_nosync(cpu_pmu->single_irq);
+ atomic_add(num_online, &cpu_pmu->remaining_work);
+ smp_mb__after_atomic();
+
+ for_each_online_cpu(cpu) {
+ struct arm_pmu_work *work = per_cpu_ptr(cpu_pmu->work, cpu);
+
+ if (cpu == smp_processor_id())
+ continue;
+
+ /*
+ * We can be extremely relaxed about memory ordering
+ * here. All we are doing is gathering information
+ * about the past to help us give a return value that
+ * will keep the spurious interrupt detector both happy
+ * *and* functional. We are not shared so we can
+ * tolerate the occasional spurious IRQ_HANDLED.
+ */
+ cret = atomic_read(&work->ret);
+ if (cret != IRQ_NONE)
+ ret = cret;
+
+ if (!irq_work_queue_on(&work->work, cpu))
+ atomic_dec(&cpu_pmu->remaining_work);
+ }
+
+ if (atomic_dec_and_test(&cpu_pmu->remaining_work))
+ enable_irq(cpu_pmu->single_irq);
+
+ return ret;
+}
+
static void cpu_pmu_free_irq(struct arm_pmu *cpu_pmu)
{
int i, irq, irqs;
@@ -107,6 +176,9 @@ static void cpu_pmu_free_irq(struct arm_pmu *cpu_pmu)
if (irq >= 0)
free_irq(irq, cpu_pmu);
}
+
+ cpu_pmu->handle_irq_none = cpu_pmu_handle_irq_none;
+ free_percpu(cpu_pmu->work);
}
}
@@ -162,6 +234,28 @@ static int cpu_pmu_request_irq(struct arm_pmu *cpu_pmu, irq_handler_t handler)
cpumask_set_cpu(i, &cpu_pmu->active_irqs);
}
+
+ /*
+ * If we are running SMP and have only one interrupt source
+ * then get ready to share that single irq among the cores.
+ */
+ if (nr_cpu_ids > 1 && irqs == 1) {
+ cpu_pmu->single_irq = platform_get_irq(pmu_device, 0);
+ cpu_pmu->work = alloc_percpu(struct arm_pmu_work);
+ if (!cpu_pmu->work) {
+ pr_err("no memory for shared IRQ workaround\n");
+ return -ENOMEM;
+ }
+
+ for_each_possible_cpu(i) {
+ struct arm_pmu_work *w =
+ per_cpu_ptr(cpu_pmu->work, i);
+ init_irq_work(&w->work, cpu_pmu_do_percpu_work);
+ w->arm_pmu = cpu_pmu;
+ }
+
+ cpu_pmu->handle_irq_none = cpu_pmu_handle_irq_none;
+ }
}
return 0;
diff --git a/arch/arm/mach-ux500/cpu-db8500.c b/arch/arm/mach-ux500/cpu-db8500.c
index 6f63954c8bde..917774999c5c 100644
--- a/arch/arm/mach-ux500/cpu-db8500.c
+++ b/arch/arm/mach-ux500/cpu-db8500.c
@@ -12,8 +12,6 @@
#include <linux/init.h>
#include <linux/device.h>
#include <linux/amba/bus.h>
-#include <linux/interrupt.h>
-#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/mfd/abx500/ab8500.h>
@@ -23,7 +21,6 @@
#include <linux/regulator/machine.h>
#include <linux/random.h>
-#include <asm/pmu.h>
#include <asm/mach/map.h>
#include "setup.h"
@@ -99,30 +96,6 @@ static void __init u8500_map_io(void)
iotable_init(u8500_io_desc, ARRAY_SIZE(u8500_io_desc));
}
-/*
- * The PMU IRQ lines of two cores are wired together into a single interrupt.
- * Bounce the interrupt to the other core if it's not ours.
- */
-static irqreturn_t db8500_pmu_handler(int irq, void *dev, irq_handler_t handler)
-{
- irqreturn_t ret = handler(irq, dev);
- int other = !smp_processor_id();
-
- if (ret == IRQ_NONE && cpu_online(other))
- irq_set_affinity(irq, cpumask_of(other));
-
- /*
- * We should be able to get away with the amount of IRQ_NONEs we give,
- * while still having the spurious IRQ detection code kick in if the
- * interrupt really starts hitting spuriously.
- */
- return ret;
-}
-
-static struct arm_pmu_platdata db8500_pmu_platdata = {
- .handle_irq = db8500_pmu_handler,
-};
-
static const char *db8500_read_soc_id(void)
{
void __iomem *uid = __io_address(U8500_BB_UID_BASE);
@@ -143,8 +116,6 @@ static struct device * __init db8500_soc_device_init(void)
}
static struct of_dev_auxdata u8500_auxdata_lookup[] __initdata = {
- /* Requires call-back bindings. */
- OF_DEV_AUXDATA("arm,cortex-a9-pmu", 0, "arm-pmu", &db8500_pmu_platdata),
/* Requires DMA bindings. */
OF_DEV_AUXDATA("stericsson,ux500-msp-i2s", 0x80123000,
"ux500-msp-i2s.0", &msp0_platform_data),