Skip to content

Commit

Permalink
ARM: 6064/1: pmu: register IRQs at runtime
Browse files Browse the repository at this point in the history
The current PMU infrastructure for ARM requires that the IRQs for the PMU
device are fixed at compile time and are selected based on the ARCH_ or MACH_ flags. This has the disadvantage of tying the Kernel down to a
particular board as far as profiling is concerned.

This patch replaces the compile-time IRQ registration with a runtime mechanism which allows the IRQs to be registered with the framework as
a platform_device.

A further advantage of this change is that there is scope for registering
different types of performance counters in the future by changing the id
of the platform_device and attaching different resources to it.

Acked-by: Jamie Iles <jamie.iles@picochip.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
  • Loading branch information
Will Deacon authored and Russell King committed May 17, 2010
1 parent c39e52a commit 49c006b
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 80 deletions.
27 changes: 12 additions & 15 deletions arch/arm/include/asm/pmu.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,26 @@ enum arm_pmu_type {

#ifdef CONFIG_CPU_HAS_PMU

struct pmu_irqs {
const int *irqs;
int num_irqs;
};

/**
* reserve_pmu() - reserve the hardware performance counters
*
* Reserve the hardware performance counters in the system for exclusive use.
* The 'struct pmu_irqs' for the system is returned on success, ERR_PTR()
* The platform_device for the system is returned on success, ERR_PTR()
* encoded error on failure.
*/
extern const struct pmu_irqs *
reserve_pmu(void);
extern struct platform_device *
reserve_pmu(enum arm_pmu_type device);

/**
* release_pmu() - Relinquish control of the performance counters
*
* Release the performance counters and allow someone else to use them.
* Callers must have disabled the counters and released IRQs before calling
* this. The 'struct pmu_irqs' returned from reserve_pmu() must be passed as
* this. The platform_device returned from reserve_pmu() must be passed as
* a cookie.
*/
extern int
release_pmu(const struct pmu_irqs *irqs);
release_pmu(struct platform_device *pdev);

/**
* init_pmu() - Initialise the PMU.
Expand All @@ -53,24 +48,26 @@ release_pmu(const struct pmu_irqs *irqs);
* the actual hardware initialisation.
*/
extern int
init_pmu(void);
init_pmu(enum arm_pmu_type device);

#else /* CONFIG_CPU_HAS_PMU */

static inline const struct pmu_irqs *
reserve_pmu(void)
#include <linux/err.h>

static inline struct platform_device *
reserve_pmu(enum arm_pmu_type device)
{
return ERR_PTR(-ENODEV);
}

static inline int
release_pmu(const struct pmu_irqs *irqs)
release_pmu(struct platform_device *pdev)
{
return -ENODEV;
}

static inline int
init_pmu(void)
init_pmu(enum arm_pmu_type device)
{
return -ENODEV;
}
Expand Down
52 changes: 31 additions & 21 deletions arch/arm/kernel/perf_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/perf_event.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>

Expand All @@ -26,7 +27,7 @@
#include <asm/pmu.h>
#include <asm/stacktrace.h>

static const struct pmu_irqs *pmu_irqs;
static struct platform_device *pmu_device;

/*
* Hardware lock to serialize accesses to PMU registers. Needed for the
Expand Down Expand Up @@ -314,38 +315,44 @@ validate_group(struct perf_event *event)
static int
armpmu_reserve_hardware(void)
{
int i;
int err;
int i, err = -ENODEV, irq;

pmu_irqs = reserve_pmu();
if (IS_ERR(pmu_irqs)) {
pmu_device = reserve_pmu(ARM_PMU_DEVICE_CPU);
if (IS_ERR(pmu_device)) {
pr_warning("unable to reserve pmu\n");
return PTR_ERR(pmu_irqs);
return PTR_ERR(pmu_device);
}

init_pmu();
init_pmu(ARM_PMU_DEVICE_CPU);

if (pmu_irqs->num_irqs < 1) {
if (pmu_device->num_resources < 1) {
pr_err("no irqs for PMUs defined\n");
return -ENODEV;
}

for (i = 0; i < pmu_irqs->num_irqs; ++i) {
err = request_irq(pmu_irqs->irqs[i], armpmu->handle_irq,
for (i = 0; i < pmu_device->num_resources; ++i) {
irq = platform_get_irq(pmu_device, i);
if (irq < 0)
continue;

err = request_irq(irq, armpmu->handle_irq,
IRQF_DISABLED | IRQF_NOBALANCING,
"armpmu", NULL);
if (err) {
pr_warning("unable to request IRQ%d for ARM "
"perf counters\n", pmu_irqs->irqs[i]);
pr_warning("unable to request IRQ%d for ARM perf "
"counters\n", irq);
break;
}
}

if (err) {
for (i = i - 1; i >= 0; --i)
free_irq(pmu_irqs->irqs[i], NULL);
release_pmu(pmu_irqs);
pmu_irqs = NULL;
for (i = i - 1; i >= 0; --i) {
irq = platform_get_irq(pmu_device, i);
if (irq >= 0)
free_irq(irq, NULL);
}
release_pmu(pmu_device);
pmu_device = NULL;
}

return err;
Expand All @@ -354,14 +361,17 @@ armpmu_reserve_hardware(void)
static void
armpmu_release_hardware(void)
{
int i;
int i, irq;

for (i = pmu_irqs->num_irqs - 1; i >= 0; --i)
free_irq(pmu_irqs->irqs[i], NULL);
for (i = pmu_device->num_resources - 1; i >= 0; --i) {
irq = platform_get_irq(pmu_device, i);
if (irq >= 0)
free_irq(irq, NULL);
}
armpmu->stop();

release_pmu(pmu_irqs);
pmu_irqs = NULL;
release_pmu(pmu_device);
pmu_device = NULL;
}

static atomic_t active_events = ATOMIC_INIT(0);
Expand Down
127 changes: 83 additions & 44 deletions arch/arm/kernel/pmu.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,86 @@
* linux/arch/arm/kernel/pmu.c
*
* Copyright (C) 2009 picoChip Designs Ltd, Jamie Iles
* Copyright (C) 2010 ARM Ltd, Will Deacon
*
* 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.
*
*/

#define pr_fmt(fmt) "PMU: " fmt

#include <linux/cpumask.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#include <asm/pmu.h>

/*
* Define the IRQs for the system. We could use something like a platform
* device but that seems fairly heavyweight for this. Also, the performance
* counters can't be removed or hotplugged.
*
* Ordering is important: init_pmu() will use the ordering to set the affinity
* to the corresponding core. e.g. the first interrupt will go to cpu 0, the
* second goes to cpu 1 etc.
*/
static const int irqs[] = {
#if defined(CONFIG_ARCH_OMAP2)
3,
#elif defined(CONFIG_ARCH_BCMRING)
IRQ_PMUIRQ,
#elif defined(CONFIG_MACH_REALVIEW_EB)
IRQ_EB11MP_PMU_CPU0,
IRQ_EB11MP_PMU_CPU1,
IRQ_EB11MP_PMU_CPU2,
IRQ_EB11MP_PMU_CPU3,
#elif defined(CONFIG_ARCH_OMAP3)
INT_34XX_BENCH_MPU_EMUL,
#elif defined(CONFIG_ARCH_IOP32X)
IRQ_IOP32X_CORE_PMU,
#elif defined(CONFIG_ARCH_IOP33X)
IRQ_IOP33X_CORE_PMU,
#elif defined(CONFIG_ARCH_PXA)
IRQ_PMU,
#endif
};
static volatile long pmu_lock;

static struct platform_device *pmu_devices[ARM_NUM_PMU_DEVICES];

static int __devinit pmu_device_probe(struct platform_device *pdev)
{

if (pdev->id < 0 || pdev->id >= ARM_NUM_PMU_DEVICES) {
pr_warning("received registration request for unknown "
"device %d\n", pdev->id);
return -EINVAL;
}

if (pmu_devices[pdev->id])
pr_warning("registering new PMU device type %d overwrites "
"previous registration!\n", pdev->id);
else
pr_info("registered new PMU device of type %d\n",
pdev->id);

static const struct pmu_irqs pmu_irqs = {
.irqs = irqs,
.num_irqs = ARRAY_SIZE(irqs),
pmu_devices[pdev->id] = pdev;
return 0;
}

static struct platform_driver pmu_driver = {
.driver = {
.name = "arm-pmu",
},
.probe = pmu_device_probe,
};

static volatile long pmu_lock;
static int __init register_pmu_driver(void)
{
return platform_driver_register(&pmu_driver);
}
device_initcall(register_pmu_driver);

const struct pmu_irqs *
reserve_pmu(void)
struct platform_device *
reserve_pmu(enum arm_pmu_type device)
{
return test_and_set_bit_lock(0, &pmu_lock) ? ERR_PTR(-EBUSY) :
&pmu_irqs;
struct platform_device *pdev;

if (test_and_set_bit_lock(device, &pmu_lock)) {
pdev = ERR_PTR(-EBUSY);
} else if (pmu_devices[device] == NULL) {
clear_bit_unlock(device, &pmu_lock);
pdev = ERR_PTR(-ENODEV);
} else {
pdev = pmu_devices[device];
}

return pdev;
}
EXPORT_SYMBOL_GPL(reserve_pmu);

int
release_pmu(const struct pmu_irqs *irqs)
release_pmu(struct platform_device *pdev)
{
if (WARN_ON(irqs != &pmu_irqs))
if (WARN_ON(pdev != pmu_devices[pdev->id]))
return -EINVAL;
clear_bit_unlock(0, &pmu_lock);
clear_bit_unlock(pdev->id, &pmu_lock);
return 0;
}
EXPORT_SYMBOL_GPL(release_pmu);
Expand All @@ -87,17 +101,42 @@ set_irq_affinity(int irq,
#endif
}

int
init_pmu(void)
static int
init_cpu_pmu(void)
{
int i, err = 0;
struct platform_device *pdev = pmu_devices[ARM_PMU_DEVICE_CPU];

if (!pdev) {
err = -ENODEV;
goto out;
}

for (i = 0; i < pmu_irqs.num_irqs; ++i) {
err = set_irq_affinity(pmu_irqs.irqs[i], i);
for (i = 0; i < pdev->num_resources; ++i) {
err = set_irq_affinity(platform_get_irq(pdev, i), i);
if (err)
break;
}

out:
return err;
}

int
init_pmu(enum arm_pmu_type device)
{
int err = 0;

switch (device) {
case ARM_PMU_DEVICE_CPU:
err = init_cpu_pmu();
break;
default:
pr_warning("attempt to initialise unknown device %d\n",
device);
err = -EINVAL;
}

return err;
}
EXPORT_SYMBOL_GPL(init_pmu);

0 comments on commit 49c006b

Please sign in to comment.