Set of ARM CCN PMU driver updates:

- fixed a nasty bitfield mangling bug
 - added new hints to the perf userspace tool
 - pinned events processing to a single PMU
 - modified events initialisation so they can be rotated now
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQEcBAABAgAGBQJVQ7fuAAoJEL9jRaJfXa5Pr6EH+wd92YXgpFHy4x0O77GdK/gk
 ErzfFlq/3GX6V+1WkMn0Nfj1vhl/Mq9RF4nSbmEU4c5x0ov48J67b94peo9RChfF
 +BjaLw0mNijpCvcFYNIOW4qvJr5LKizvaq3uzw7h7AFl24MwRIKrNXJP6qON04sB
 9TCekVCWutKORkEmVpl4Wltsb3LpqNwo/ziby2dmgRCsxPeXnOycsk45/4Tc0GSg
 oR5MXLAuvxvNivNMF83FphiAeqPu1eeSWYkT468qqarr4NWKpbYd6uNTMViVHUcW
 Yd6pxUwB7dphmLckDOIuJ9QJOfxL263uHaq1taUm1HVp15ldKfWapgVH4hIsUj8=
 =mXhn
 -----END PGP SIGNATURE-----

Merge tag 'ccn/updates-for-4.2' of git://git.linaro.org/people/pawel.moll/linux into next/drivers

Pull "Set of ARM CCN PMU driver updates" from Pawel Moll:

- fixed a nasty bitfield mangling bug
- added new hints to the perf userspace tool
- pinned events processing to a single PMU
- modified events initialisation so they can be rotated now

* tag 'ccn/updates-for-4.2' of git://git.linaro.org/people/pawel.moll/linux:
  bus: arm-ccn: Allocate event when it is being added, not initialised
  bus: arm-ccn: Do not group CCN events with other PMUs
  bus: arm-ccn: Provide required event arguments
  bus: arm-ccn: cpumask attribute
  bus: arm-ccn: Fix node->XP config conversion
This commit is contained in:
Arnd Bergmann 2015-05-12 16:47:51 +02:00
commit 11f52002bf
2 changed files with 226 additions and 64 deletions

View File

@ -33,20 +33,23 @@ directory, with first 8 configurable by user and additional
Cycle counter is described by a "type" value 0xff and does Cycle counter is described by a "type" value 0xff and does
not require any other settings. not require any other settings.
The driver also provides a "cpumask" sysfs attribute, which contains
a single CPU ID, of the processor which will be used to handle all
the CCN PMU events. It is recommended that the user space tools
request the events on this processor (if not, the perf_event->cpu value
will be overwritten anyway). In case of this processor being offlined,
the events are migrated to another one and the attribute is updated.
Example of perf tool use: Example of perf tool use:
/ # perf list | grep ccn / # perf list | grep ccn
ccn/cycles/ [Kernel PMU event] ccn/cycles/ [Kernel PMU event]
<...> <...>
ccn/xp_valid_flit/ [Kernel PMU event] ccn/xp_valid_flit,xp=?,port=?,vc=?,dir=?/ [Kernel PMU event]
<...> <...>
/ # perf stat -C 0 -e ccn/cycles/,ccn/xp_valid_flit,xp=1,port=0,vc=1,dir=1/ \ / # perf stat -a -e ccn/cycles/,ccn/xp_valid_flit,xp=1,port=0,vc=1,dir=1/ \
sleep 1 sleep 1
The driver does not support sampling, therefore "perf record" will The driver does not support sampling, therefore "perf record" will
not work. Also notice that only single cpu is being selected not work. Per-task (without "-a") perf sessions are not supported.
("-C 0") - this is because perf framework does not support
"non-CPU related" counters (yet?) so system-wide session ("-a")
would try (and in most cases fail) to set up the same event
per each CPU.

View File

@ -166,13 +166,17 @@ struct arm_ccn_dt {
struct hrtimer hrtimer; struct hrtimer hrtimer;
cpumask_t cpu;
struct notifier_block cpu_nb;
struct pmu pmu; struct pmu pmu;
}; };
struct arm_ccn { struct arm_ccn {
struct device *dev; struct device *dev;
void __iomem *base; void __iomem *base;
unsigned irq_used:1; unsigned int irq;
unsigned sbas_present:1; unsigned sbas_present:1;
unsigned sbsx_present:1; unsigned sbsx_present:1;
@ -212,7 +216,7 @@ static int arm_ccn_node_to_xp_port(int node)
static void arm_ccn_pmu_config_set(u64 *config, u32 node_xp, u32 type, u32 port) static void arm_ccn_pmu_config_set(u64 *config, u32 node_xp, u32 type, u32 port)
{ {
*config &= ~((0xff << 0) | (0xff << 8) | (0xff << 24)); *config &= ~((0xff << 0) | (0xff << 8) | (0x3 << 24));
*config |= (node_xp << 0) | (type << 8) | (port << 24); *config |= (node_xp << 0) | (type << 8) | (port << 24);
} }
@ -336,6 +340,23 @@ static ssize_t arm_ccn_pmu_event_show(struct device *dev,
if (event->mask) if (event->mask)
res += snprintf(buf + res, PAGE_SIZE - res, ",mask=0x%x", res += snprintf(buf + res, PAGE_SIZE - res, ",mask=0x%x",
event->mask); event->mask);
/* Arguments required by an event */
switch (event->type) {
case CCN_TYPE_CYCLES:
break;
case CCN_TYPE_XP:
res += snprintf(buf + res, PAGE_SIZE - res,
",xp=?,port=?,vc=?,dir=?");
if (event->event == CCN_EVENT_WATCHPOINT)
res += snprintf(buf + res, PAGE_SIZE - res,
",cmp_l=?,cmp_h=?,mask=?");
break;
default:
res += snprintf(buf + res, PAGE_SIZE - res, ",node=?");
break;
}
res += snprintf(buf + res, PAGE_SIZE - res, "\n"); res += snprintf(buf + res, PAGE_SIZE - res, "\n");
return res; return res;
@ -521,6 +542,25 @@ static struct attribute_group arm_ccn_pmu_cmp_mask_attr_group = {
.attrs = arm_ccn_pmu_cmp_mask_attrs, .attrs = arm_ccn_pmu_cmp_mask_attrs,
}; };
static ssize_t arm_ccn_pmu_cpumask_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct arm_ccn *ccn = pmu_to_arm_ccn(dev_get_drvdata(dev));
return cpumap_print_to_pagebuf(true, buf, &ccn->dt.cpu);
}
static struct device_attribute arm_ccn_pmu_cpumask_attr =
__ATTR(cpumask, S_IRUGO, arm_ccn_pmu_cpumask_show, NULL);
static struct attribute *arm_ccn_pmu_cpumask_attrs[] = {
&arm_ccn_pmu_cpumask_attr.attr,
NULL,
};
static struct attribute_group arm_ccn_pmu_cpumask_attr_group = {
.attrs = arm_ccn_pmu_cpumask_attrs,
};
/* /*
* Default poll period is 10ms, which is way over the top anyway, * Default poll period is 10ms, which is way over the top anyway,
@ -542,6 +582,7 @@ static const struct attribute_group *arm_ccn_pmu_attr_groups[] = {
&arm_ccn_pmu_events_attr_group, &arm_ccn_pmu_events_attr_group,
&arm_ccn_pmu_format_attr_group, &arm_ccn_pmu_format_attr_group,
&arm_ccn_pmu_cmp_mask_attr_group, &arm_ccn_pmu_cmp_mask_attr_group,
&arm_ccn_pmu_cpumask_attr_group,
NULL NULL
}; };
@ -587,7 +628,65 @@ static int arm_ccn_pmu_type_eq(u32 a, u32 b)
return 0; return 0;
} }
static void arm_ccn_pmu_event_destroy(struct perf_event *event) static int arm_ccn_pmu_event_alloc(struct perf_event *event)
{
struct arm_ccn *ccn = pmu_to_arm_ccn(event->pmu);
struct hw_perf_event *hw = &event->hw;
u32 node_xp, type, event_id;
struct arm_ccn_component *source;
int bit;
node_xp = CCN_CONFIG_NODE(event->attr.config);
type = CCN_CONFIG_TYPE(event->attr.config);
event_id = CCN_CONFIG_EVENT(event->attr.config);
/* Allocate the cycle counter */
if (type == CCN_TYPE_CYCLES) {
if (test_and_set_bit(CCN_IDX_PMU_CYCLE_COUNTER,
ccn->dt.pmu_counters_mask))
return -EAGAIN;
hw->idx = CCN_IDX_PMU_CYCLE_COUNTER;
ccn->dt.pmu_counters[CCN_IDX_PMU_CYCLE_COUNTER].event = event;
return 0;
}
/* Allocate an event counter */
hw->idx = arm_ccn_pmu_alloc_bit(ccn->dt.pmu_counters_mask,
CCN_NUM_PMU_EVENT_COUNTERS);
if (hw->idx < 0) {
dev_dbg(ccn->dev, "No more counters available!\n");
return -EAGAIN;
}
if (type == CCN_TYPE_XP)
source = &ccn->xp[node_xp];
else
source = &ccn->node[node_xp];
ccn->dt.pmu_counters[hw->idx].source = source;
/* Allocate an event source or a watchpoint */
if (type == CCN_TYPE_XP && event_id == CCN_EVENT_WATCHPOINT)
bit = arm_ccn_pmu_alloc_bit(source->xp.dt_cmp_mask,
CCN_NUM_XP_WATCHPOINTS);
else
bit = arm_ccn_pmu_alloc_bit(source->pmu_events_mask,
CCN_NUM_PMU_EVENTS);
if (bit < 0) {
dev_dbg(ccn->dev, "No more event sources/watchpoints on node/XP %d!\n",
node_xp);
clear_bit(hw->idx, ccn->dt.pmu_counters_mask);
return -EAGAIN;
}
hw->config_base = bit;
ccn->dt.pmu_counters[hw->idx].event = event;
return 0;
}
static void arm_ccn_pmu_event_release(struct perf_event *event)
{ {
struct arm_ccn *ccn = pmu_to_arm_ccn(event->pmu); struct arm_ccn *ccn = pmu_to_arm_ccn(event->pmu);
struct hw_perf_event *hw = &event->hw; struct hw_perf_event *hw = &event->hw;
@ -616,15 +715,14 @@ static int arm_ccn_pmu_event_init(struct perf_event *event)
struct arm_ccn *ccn; struct arm_ccn *ccn;
struct hw_perf_event *hw = &event->hw; struct hw_perf_event *hw = &event->hw;
u32 node_xp, type, event_id; u32 node_xp, type, event_id;
int valid, bit; int valid;
struct arm_ccn_component *source;
int i; int i;
struct perf_event *sibling;
if (event->attr.type != event->pmu->type) if (event->attr.type != event->pmu->type)
return -ENOENT; return -ENOENT;
ccn = pmu_to_arm_ccn(event->pmu); ccn = pmu_to_arm_ccn(event->pmu);
event->destroy = arm_ccn_pmu_event_destroy;
if (hw->sample_period) { if (hw->sample_period) {
dev_warn(ccn->dev, "Sampling not supported!\n"); dev_warn(ccn->dev, "Sampling not supported!\n");
@ -642,6 +740,16 @@ static int arm_ccn_pmu_event_init(struct perf_event *event)
dev_warn(ccn->dev, "Can't provide per-task data!\n"); dev_warn(ccn->dev, "Can't provide per-task data!\n");
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
/*
* Many perf core operations (eg. events rotation) operate on a
* single CPU context. This is obvious for CPU PMUs, where one
* expects the same sets of events being observed on all CPUs,
* but can lead to issues for off-core PMUs, like CCN, where each
* event could be theoretically assigned to a different CPU. To
* mitigate this, we enforce CPU assignment to one, selected
* processor (the one described in the "cpumask" attribute).
*/
event->cpu = cpumask_first(&ccn->dt.cpu);
node_xp = CCN_CONFIG_NODE(event->attr.config); node_xp = CCN_CONFIG_NODE(event->attr.config);
type = CCN_CONFIG_TYPE(event->attr.config); type = CCN_CONFIG_TYPE(event->attr.config);
@ -711,48 +819,20 @@ static int arm_ccn_pmu_event_init(struct perf_event *event)
node_xp, type, port); node_xp, type, port);
} }
/* Allocate the cycle counter */ /*
if (type == CCN_TYPE_CYCLES) { * We must NOT create groups containing mixed PMUs, although software
if (test_and_set_bit(CCN_IDX_PMU_CYCLE_COUNTER, * events are acceptable (for example to create a CCN group
ccn->dt.pmu_counters_mask)) * periodically read when a hrtimer aka cpu-clock leader triggers).
return -EAGAIN; */
if (event->group_leader->pmu != event->pmu &&
!is_software_event(event->group_leader))
return -EINVAL;
hw->idx = CCN_IDX_PMU_CYCLE_COUNTER; list_for_each_entry(sibling, &event->group_leader->sibling_list,
ccn->dt.pmu_counters[CCN_IDX_PMU_CYCLE_COUNTER].event = event; group_entry)
if (sibling->pmu != event->pmu &&
return 0; !is_software_event(sibling))
} return -EINVAL;
/* Allocate an event counter */
hw->idx = arm_ccn_pmu_alloc_bit(ccn->dt.pmu_counters_mask,
CCN_NUM_PMU_EVENT_COUNTERS);
if (hw->idx < 0) {
dev_warn(ccn->dev, "No more counters available!\n");
return -EAGAIN;
}
if (type == CCN_TYPE_XP)
source = &ccn->xp[node_xp];
else
source = &ccn->node[node_xp];
ccn->dt.pmu_counters[hw->idx].source = source;
/* Allocate an event source or a watchpoint */
if (type == CCN_TYPE_XP && event_id == CCN_EVENT_WATCHPOINT)
bit = arm_ccn_pmu_alloc_bit(source->xp.dt_cmp_mask,
CCN_NUM_XP_WATCHPOINTS);
else
bit = arm_ccn_pmu_alloc_bit(source->pmu_events_mask,
CCN_NUM_PMU_EVENTS);
if (bit < 0) {
dev_warn(ccn->dev, "No more event sources/watchpoints on node/XP %d!\n",
node_xp);
clear_bit(hw->idx, ccn->dt.pmu_counters_mask);
return -EAGAIN;
}
hw->config_base = bit;
ccn->dt.pmu_counters[hw->idx].event = event;
return 0; return 0;
} }
@ -835,9 +915,15 @@ static void arm_ccn_pmu_event_start(struct perf_event *event, int flags)
arm_ccn_pmu_read_counter(ccn, hw->idx)); arm_ccn_pmu_read_counter(ccn, hw->idx));
hw->state = 0; hw->state = 0;
if (!ccn->irq_used) /*
hrtimer_start(&ccn->dt.hrtimer, arm_ccn_pmu_timer_period(), * Pin the timer, so that the overflows are handled by the chosen
HRTIMER_MODE_REL); * event->cpu (this is the same one as presented in "cpumask"
* attribute).
*/
if (!ccn->irq)
__hrtimer_start_range_ns(&ccn->dt.hrtimer,
arm_ccn_pmu_timer_period(), 0,
HRTIMER_MODE_REL_PINNED, 0);
/* Set the DT bus input, engaging the counter */ /* Set the DT bus input, engaging the counter */
arm_ccn_pmu_xp_dt_config(event, 1); arm_ccn_pmu_xp_dt_config(event, 1);
@ -852,7 +938,7 @@ static void arm_ccn_pmu_event_stop(struct perf_event *event, int flags)
/* Disable counting, setting the DT bus to pass-through mode */ /* Disable counting, setting the DT bus to pass-through mode */
arm_ccn_pmu_xp_dt_config(event, 0); arm_ccn_pmu_xp_dt_config(event, 0);
if (!ccn->irq_used) if (!ccn->irq)
hrtimer_cancel(&ccn->dt.hrtimer); hrtimer_cancel(&ccn->dt.hrtimer);
/* Let the DT bus drain */ /* Let the DT bus drain */
@ -1014,8 +1100,13 @@ static void arm_ccn_pmu_event_config(struct perf_event *event)
static int arm_ccn_pmu_event_add(struct perf_event *event, int flags) static int arm_ccn_pmu_event_add(struct perf_event *event, int flags)
{ {
int err;
struct hw_perf_event *hw = &event->hw; struct hw_perf_event *hw = &event->hw;
err = arm_ccn_pmu_event_alloc(event);
if (err)
return err;
arm_ccn_pmu_event_config(event); arm_ccn_pmu_event_config(event);
hw->state = PERF_HES_STOPPED; hw->state = PERF_HES_STOPPED;
@ -1029,6 +1120,8 @@ static int arm_ccn_pmu_event_add(struct perf_event *event, int flags)
static void arm_ccn_pmu_event_del(struct perf_event *event, int flags) static void arm_ccn_pmu_event_del(struct perf_event *event, int flags)
{ {
arm_ccn_pmu_event_stop(event, PERF_EF_UPDATE); arm_ccn_pmu_event_stop(event, PERF_EF_UPDATE);
arm_ccn_pmu_event_release(event);
} }
static void arm_ccn_pmu_event_read(struct perf_event *event) static void arm_ccn_pmu_event_read(struct perf_event *event)
@ -1079,12 +1172,39 @@ static enum hrtimer_restart arm_ccn_pmu_timer_handler(struct hrtimer *hrtimer)
} }
static int arm_ccn_pmu_cpu_notifier(struct notifier_block *nb,
unsigned long action, void *hcpu)
{
struct arm_ccn_dt *dt = container_of(nb, struct arm_ccn_dt, cpu_nb);
struct arm_ccn *ccn = container_of(dt, struct arm_ccn, dt);
unsigned int cpu = (long)hcpu; /* for (long) see kernel/cpu.c */
unsigned int target;
switch (action & ~CPU_TASKS_FROZEN) {
case CPU_DOWN_PREPARE:
if (!cpumask_test_and_clear_cpu(cpu, &dt->cpu))
break;
target = cpumask_any_but(cpu_online_mask, cpu);
if (target < 0)
break;
perf_pmu_migrate_context(&dt->pmu, cpu, target);
cpumask_set_cpu(target, &dt->cpu);
WARN_ON(irq_set_affinity(ccn->irq, &dt->cpu) != 0);
default:
break;
}
return NOTIFY_OK;
}
static DEFINE_IDA(arm_ccn_pmu_ida); static DEFINE_IDA(arm_ccn_pmu_ida);
static int arm_ccn_pmu_init(struct arm_ccn *ccn) static int arm_ccn_pmu_init(struct arm_ccn *ccn)
{ {
int i; int i;
char *name; char *name;
int err;
/* Initialize DT subsystem */ /* Initialize DT subsystem */
ccn->dt.base = ccn->base + CCN_REGION_SIZE; ccn->dt.base = ccn->base + CCN_REGION_SIZE;
@ -1136,20 +1256,58 @@ static int arm_ccn_pmu_init(struct arm_ccn *ccn)
}; };
/* No overflow interrupt? Have to use a timer instead. */ /* No overflow interrupt? Have to use a timer instead. */
if (!ccn->irq_used) { if (!ccn->irq) {
dev_info(ccn->dev, "No access to interrupts, using timer.\n"); dev_info(ccn->dev, "No access to interrupts, using timer.\n");
hrtimer_init(&ccn->dt.hrtimer, CLOCK_MONOTONIC, hrtimer_init(&ccn->dt.hrtimer, CLOCK_MONOTONIC,
HRTIMER_MODE_REL); HRTIMER_MODE_REL);
ccn->dt.hrtimer.function = arm_ccn_pmu_timer_handler; ccn->dt.hrtimer.function = arm_ccn_pmu_timer_handler;
} }
return perf_pmu_register(&ccn->dt.pmu, name, -1); /* Pick one CPU which we will use to collect data from CCN... */
cpumask_set_cpu(smp_processor_id(), &ccn->dt.cpu);
/*
* ... and change the selection when it goes offline. Priority is
* picked to have a chance to migrate events before perf is notified.
*/
ccn->dt.cpu_nb.notifier_call = arm_ccn_pmu_cpu_notifier;
ccn->dt.cpu_nb.priority = CPU_PRI_PERF + 1,
err = register_cpu_notifier(&ccn->dt.cpu_nb);
if (err)
goto error_cpu_notifier;
/* Also make sure that the overflow interrupt is handled by this CPU */
if (ccn->irq) {
err = irq_set_affinity(ccn->irq, &ccn->dt.cpu);
if (err) {
dev_err(ccn->dev, "Failed to set interrupt affinity!\n");
goto error_set_affinity;
}
}
err = perf_pmu_register(&ccn->dt.pmu, name, -1);
if (err)
goto error_pmu_register;
return 0;
error_pmu_register:
error_set_affinity:
unregister_cpu_notifier(&ccn->dt.cpu_nb);
error_cpu_notifier:
ida_simple_remove(&arm_ccn_pmu_ida, ccn->dt.id);
for (i = 0; i < ccn->num_xps; i++)
writel(0, ccn->xp[i].base + CCN_XP_DT_CONTROL);
writel(0, ccn->dt.base + CCN_DT_PMCR);
return err;
} }
static void arm_ccn_pmu_cleanup(struct arm_ccn *ccn) static void arm_ccn_pmu_cleanup(struct arm_ccn *ccn)
{ {
int i; int i;
irq_set_affinity(ccn->irq, cpu_possible_mask);
unregister_cpu_notifier(&ccn->dt.cpu_nb);
for (i = 0; i < ccn->num_xps; i++) for (i = 0; i < ccn->num_xps; i++)
writel(0, ccn->xp[i].base + CCN_XP_DT_CONTROL); writel(0, ccn->xp[i].base + CCN_XP_DT_CONTROL);
writel(0, ccn->dt.base + CCN_DT_PMCR); writel(0, ccn->dt.base + CCN_DT_PMCR);
@ -1285,6 +1443,7 @@ static int arm_ccn_probe(struct platform_device *pdev)
{ {
struct arm_ccn *ccn; struct arm_ccn *ccn;
struct resource *res; struct resource *res;
unsigned int irq;
int err; int err;
ccn = devm_kzalloc(&pdev->dev, sizeof(*ccn), GFP_KERNEL); ccn = devm_kzalloc(&pdev->dev, sizeof(*ccn), GFP_KERNEL);
@ -1309,6 +1468,7 @@ static int arm_ccn_probe(struct platform_device *pdev)
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) if (!res)
return -EINVAL; return -EINVAL;
irq = res->start;
/* Check if we can use the interrupt */ /* Check if we can use the interrupt */
writel(CCN_MN_ERRINT_STATUS__PMU_EVENTS__DISABLE, writel(CCN_MN_ERRINT_STATUS__PMU_EVENTS__DISABLE,
@ -1318,13 +1478,12 @@ static int arm_ccn_probe(struct platform_device *pdev)
/* Can set 'disable' bits, so can acknowledge interrupts */ /* Can set 'disable' bits, so can acknowledge interrupts */
writel(CCN_MN_ERRINT_STATUS__PMU_EVENTS__ENABLE, writel(CCN_MN_ERRINT_STATUS__PMU_EVENTS__ENABLE,
ccn->base + CCN_MN_ERRINT_STATUS); ccn->base + CCN_MN_ERRINT_STATUS);
err = devm_request_irq(ccn->dev, res->start, err = devm_request_irq(ccn->dev, irq, arm_ccn_irq_handler, 0,
arm_ccn_irq_handler, 0, dev_name(ccn->dev), dev_name(ccn->dev), ccn);
ccn);
if (err) if (err)
return err; return err;
ccn->irq_used = 1; ccn->irq = irq;
} }