/* * ARM performance counter support. * * Copyright (C) 2009 picoChip Designs, Ltd., Jamie Iles * Copyright (C) 2010 ARM Ltd., Will Deacon <will.deacon@arm.com> * * This code is based on the sparc64 perf event code, which is in turn based * on the x86 code.
*/ #define pr_fmt(fmt) "hw perfevents: " fmt
int
armpmu_map_event(struct perf_event *event, constunsigned (*event_map)[PERF_COUNT_HW_MAX], constunsigned (*cache_map)
[PERF_COUNT_HW_CACHE_MAX]
[PERF_COUNT_HW_CACHE_OP_MAX]
[PERF_COUNT_HW_CACHE_RESULT_MAX],
u32 raw_event_mask)
{
u64 config = event->attr.config; int type = event->attr.type;
if (type == event->pmu->type) return armpmu_map_raw_event(raw_event_mask, config);
switch (type) { case PERF_TYPE_HARDWARE: return armpmu_map_hw_event(event_map, config); case PERF_TYPE_HW_CACHE: return armpmu_map_cache_event(cache_map, config); case PERF_TYPE_RAW: return armpmu_map_raw_event(raw_event_mask, config);
}
return -ENOENT;
}
int armpmu_event_set_period(struct perf_event *event)
{ struct arm_pmu *armpmu = to_arm_pmu(event->pmu); struct hw_perf_event *hwc = &event->hw;
s64 left = local64_read(&hwc->period_left);
s64 period = hwc->sample_period;
u64 max_period; int ret = 0;
max_period = arm_pmu_event_max_period(event); if (unlikely(left <= -period)) {
left = period;
local64_set(&hwc->period_left, left);
hwc->last_period = period;
ret = 1;
}
if (unlikely(left <= 0)) {
left += period;
local64_set(&hwc->period_left, left);
hwc->last_period = period;
ret = 1;
}
/* * Limit the maximum period to prevent the counter value * from overtaking the one we are about to program. In * effect we are reducing max_period to account for * interrupt latency (and we are being very conservative).
*/ if (left > (max_period >> 1))
left = (max_period >> 1);
/* * ARM pmu always has to update the counter, so ignore * PERF_EF_UPDATE, see comments in armpmu_start().
*/ if (!(hwc->state & PERF_HES_STOPPED)) {
armpmu->disable(event);
armpmu_event_update(event);
hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
}
}
/* * ARM pmu always has to reprogram the period, so ignore * PERF_EF_RELOAD, see the comment below.
*/ if (flags & PERF_EF_RELOAD)
WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE));
hwc->state = 0; /* * Set the period again. Some counters can't be stopped, so when we * were stopped we simply disabled the IRQ source and the counter * may have been left counting. If we don't do this step then we may * get an interrupt too soon or *way* too late if the overflow has * happened since disabling.
*/
armpmu_event_set_period(event);
armpmu->enable(event);
}
/* * Reject groups spanning multiple HW PMUs (e.g. CPU + CCI). The * core perf code won't check that the pmu->ctx == leader->ctx * until after pmu->event_init(event).
*/ if (event->pmu != pmu) return 0;
if (event->state < PERF_EVENT_STATE_OFF) return 1;
if (event->state == PERF_EVENT_STATE_OFF && !event->attr.enable_on_exec) return 1;
/* * Initialise the fake PMU. We only need to populate the * used_mask for the purposes of validation.
*/
memset(&fake_pmu.used_mask, 0, sizeof(fake_pmu.used_mask));
if (!validate_event(event->pmu, &fake_pmu, leader)) return -EINVAL;
if (event == leader) return 0;
for_each_sibling_event(sibling, leader) { if (!validate_event(event->pmu, &fake_pmu, sibling)) return -EINVAL;
}
if (!validate_event(event->pmu, &fake_pmu, event)) return -EINVAL;
/* * we request the IRQ with a (possibly percpu) struct arm_pmu**, but * the handlers expect a struct arm_pmu*. The percpu_irq framework will * do any necessary shifting, we just need to perform the first * dereference.
*/
armpmu = *(void **)dev; if (WARN_ON_ONCE(!armpmu)) return IRQ_NONE;
start_clock = sched_clock();
ret = armpmu->handle_irq(armpmu);
finish_clock = sched_clock();
if (mapping < 0) {
pr_debug("event %x:%llx not supported\n", event->attr.type,
event->attr.config); return mapping;
}
/* * 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. For SMP systems, each core has it's own PMU so we can't do any * clever allocation or constraints checking at this point.
*/
hwc->idx = -1;
hwc->config_base = 0;
hwc->config = 0;
hwc->event_base = 0;
/* * Check whether we need to exclude the counter from certain modes.
*/ if (armpmu->set_event_filter) {
ret = armpmu->set_event_filter(hwc, &event->attr); if (ret) return ret;
}
/* * Store the event encoding into the config_base field.
*/
hwc->config_base |= (unsignedlong)mapping;
if (!is_sampling_event(event)) { /* * For non-sampling runs, limit the sample_period to half * of the counter width. That way, the new counter value * is far less likely to overtake the previous one unless * you have some serious IRQ latency issues.
*/
hwc->sample_period = arm_pmu_event_max_period(event) >> 1;
hwc->last_period = hwc->sample_period;
local64_set(&hwc->period_left, hwc->sample_period);
}
/* * Reject CPU-affine events for CPUs that are of a different class to * that which this PMU handles. Process-following events (where * event->cpu == -1) can be migrated between CPUs, and thus we have to * reject them later (in armpmu_add) if they're scheduled on a * different class of CPU.
*/ if (event->cpu != -1 &&
!cpumask_test_cpu(event->cpu, &armpmu->supported_cpus)) return -ENOENT;
if (has_branch_stack(event) && !armpmu->reg_brbidr) return -EOPNOTSUPP;
/* For task-bound events we may be called on other CPUs */ if (!cpumask_test_cpu(smp_processor_id(), &armpmu->supported_cpus)) return;
armpmu->stop(armpmu);
}
/* * In heterogeneous systems, events are specific to a particular * microarchitecture, and aren't suitable for another. Thus, only match CPUs of * the same microarchitecture.
*/ staticbool armpmu_filter(struct pmu *pmu, int cpu)
{ struct arm_pmu *armpmu = to_arm_pmu(pmu); return !cpumask_test_cpu(cpu, &armpmu->supported_cpus);
}
/* If cannot get an NMI, get a normal interrupt */ if (err) {
err = request_irq(irq, handler, irq_flags, "arm-pmu",
per_cpu_ptr(&cpu_armpmu, cpu));
irq_ops = &pmuirq_ops;
} else {
has_nmi = true;
irq_ops = &pmunmi_ops;
}
} elseif (armpmu_count_irq_users(irq) == 0) {
err = request_percpu_nmi(irq, handler, "arm-pmu", &cpu_armpmu);
/* If cannot get an NMI, get a normal interrupt */ if (err) {
err = request_percpu_irq(irq, handler, "arm-pmu",
&cpu_armpmu);
irq_ops = &percpu_pmuirq_ops;
} else {
has_nmi = true;
irq_ops = &percpu_pmunmi_ops;
}
} else { /* Per cpudevid irq was already requested by another CPU */
irq_ops = armpmu_find_irq_ops(irq);
/* * PMU hardware loses all context when a CPU goes offline. * When a CPU is hotplugged back in, since some hardware registers are * UNKNOWN at reset, the PMU must be explicitly reset to avoid reading * junk values out of them.
*/ staticint arm_perf_starting_cpu(unsignedint cpu, struct hlist_node *node)
{ struct arm_pmu *pmu = hlist_entry_safe(node, struct arm_pmu, node); int irq;
if (!cpumask_test_cpu(cpu, &pmu->supported_cpus)) return 0; if (pmu->reset)
pmu->reset(pmu);
per_cpu(cpu_armpmu, cpu) = pmu;
irq = armpmu_get_cpu_irq(pmu, cpu); if (irq)
per_cpu(cpu_irq_ops, cpu)->enable_pmuirq(irq);
if (!cpumask_test_cpu(smp_processor_id(), &armpmu->supported_cpus)) return NOTIFY_DONE;
/* * Always reset the PMU registers on power-up even if * there are no events running.
*/ if (cmd == CPU_PM_EXIT && armpmu->reset)
armpmu->reset(armpmu);
if (!enabled) return NOTIFY_OK;
switch (cmd) { case CPU_PM_ENTER:
armpmu->stop(armpmu);
cpu_pm_pmu_setup(armpmu, cmd); break; case CPU_PM_EXIT: case CPU_PM_ENTER_FAILED:
cpu_pm_pmu_setup(armpmu, cmd);
armpmu->start(armpmu); break; default: return NOTIFY_DONE;
}
ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_STARTING, "perf/arm/pmu:starting",
arm_perf_starting_cpu,
arm_perf_teardown_cpu); if (ret)
pr_err("CPU hotplug notifier for ARM PMU could not be registered: %d\n",
ret); return ret;
}
subsys_initcall(arm_pmu_hp_init);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.