// SPDX-License-Identifier: GPL-2.0 /* * ARM CoreSight Architecture PMU driver. * * This driver adds support for uncore PMU based on ARM CoreSight Performance * Monitoring Unit Architecture. The PMU is accessible via MMIO registers and * like other uncore PMUs, it does not support process specific events and * cannot be used in sampling mode. * * This code is based on other uncore PMUs like ARM DSU PMU. It provides a * generic implementation to operate the PMU according to CoreSight PMU * architecture and ACPI ARM PMU table (APMT) documents below: * - ARM CoreSight PMU architecture document number: ARM IHI 0091 A.a-00bet0. * - APMT document number: ARM DEN0117. * * The user should refer to the vendor technical documentation to get details * about the supported events. * * Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. *
*/
/* * In CoreSight PMU architecture, all of the MMIO registers are 32-bit except * counter register. The counter register can be implemented as 32-bit or 64-bit * register depending on the value of PMCFGR.SIZE field. For 64-bit access, * single-copy 64-bit atomic support is implementation defined. APMT node flag * is used to identify if the PMU supports 64-bit single copy atomic. If 64-bit * single copy atomic is not supported, the driver treats the register as a pair * of 32-bit register.
*/
/* * Read 64-bit register as a pair of 32-bit registers using hi-lo-hi sequence.
*/ static u64 read_reg64_hilohi(constvoid __iomem *addr, u32 max_poll_count)
{
u32 val_lo, val_hi;
u64 val;
/* Use high-low-high sequence to avoid tearing */ do { if (max_poll_count-- == 0) {
pr_err("ARM CSPMU: timeout hi-low-high sequence\n"); return 0;
}
/* Firmware may override implementer/product ID from PMIIDR */ if (apmt_node && apmt_node->impl_id)
cspmu->impl.pmiidr = apmt_node->impl_id;
/* Find implementer specific attribute ops. */
match = arm_cspmu_impl_match_get(cspmu->impl.pmiidr);
/* Load implementer module and initialize the callbacks. */ if (match) {
mutex_lock(&arm_cspmu_lock);
if (match->impl_init_ops) { /* Prevent unload until PMU registration is done. */ if (try_module_get(match->module)) {
cspmu->impl.module = match->module;
cspmu->impl.match = match;
ret = match->impl_init_ops(cspmu); if (ret)
module_put(match->module);
} else {
WARN(1, "arm_cspmu failed to get module: %s\n",
match->module_name);
ret = -EINVAL;
}
} else {
request_module_nowait(match->module_name);
ret = -EPROBE_DEFER;
}
if (supports_cycle_counter(cspmu)) { if (cspmu->impl.ops.is_cycle_counter_event(event)) { /* Search for available cycle counter. */ if (test_and_set_bit(cspmu->cycle_counter_logical_idx,
hw_events->used_ctrs)) return -EAGAIN;
return cspmu->cycle_counter_logical_idx;
}
/* * Search a regular counter from the used counter bitmap. * The cycle counter divides the bitmap into two parts. Search * the first then second half to exclude the cycle counter bit.
*/
idx = find_first_zero_bit(hw_events->used_ctrs,
cspmu->cycle_counter_logical_idx); if (idx >= cspmu->cycle_counter_logical_idx) {
idx = find_next_zero_bit(
hw_events->used_ctrs,
cspmu->num_logical_ctrs,
cspmu->cycle_counter_logical_idx + 1);
}
} else {
idx = find_first_zero_bit(hw_events->used_ctrs,
cspmu->num_logical_ctrs);
}
if (idx >= cspmu->num_logical_ctrs) return -EAGAIN;
if (cspmu->impl.ops.validate_event) {
ret = cspmu->impl.ops.validate_event(cspmu, event); if (ret) return ret;
}
/* * Make sure the group of events can be scheduled at once * on the PMU.
*/ staticbool arm_cspmu_validate_group(struct perf_event *event)
{ struct perf_event *sibling, *leader = event->group_leader; struct arm_cspmu_hw_events fake_hw_events;
if (event->attr.type != event->pmu->type) return -ENOENT;
/* * Following other "uncore" PMUs, we do not support sampling mode or * attach to a task (per-process mode).
*/ if (is_sampling_event(event)) {
dev_dbg(cspmu->pmu.dev, "Can't support sampling events\n"); return -EOPNOTSUPP;
}
if (event->cpu < 0 || event->attach_state & PERF_ATTACH_TASK) {
dev_dbg(cspmu->pmu.dev, "Can't support per-task counters\n"); return -EINVAL;
}
/* * Make sure the CPU assignment is on one of the CPUs associated with * this PMU.
*/ if (!cpumask_test_cpu(event->cpu, &cspmu->associated_cpus)) {
dev_dbg(cspmu->pmu.dev, "Requested cpu is not associated with the PMU\n"); return -EINVAL;
}
/* Enforce the current active CPU to handle the events in this PMU. */
event->cpu = cpumask_first(&cspmu->active_cpu); if (event->cpu >= nr_cpu_ids) return -EINVAL;
if (!arm_cspmu_validate_group(event)) return -EINVAL;
/* * The logical counter id is tracked with hw_perf_event.extra_reg.idx. * The physical counter id is tracked with hw_perf_event.idx. * 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.
*/
hwc->idx = -1;
hwc->extra_reg.idx = -1;
hwc->config = cspmu->impl.ops.event_type(event);
/* * arm_cspmu_set_event_period: Set the period for the counter. * * To handle cases of extreme interrupt latency, we program * the counter with half of the max count for the counters.
*/ staticvoid arm_cspmu_set_event_period(struct perf_event *event)
{ struct arm_cspmu *cspmu = to_arm_cspmu(event->pmu);
u64 val = counter_mask(cspmu) >> 1ULL;
/* Base address for page 0. */
cspmu->base0 = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(cspmu->base0)) {
dev_err(dev, "ioremap failed for page-0 resource\n"); return PTR_ERR(cspmu->base0);
}
/* Base address for page 1 if supported. Otherwise point to page 0. */
cspmu->base1 = cspmu->base0; if (platform_get_resource(pdev, IORESOURCE_MEM, 1)) {
cspmu->base1 = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(cspmu->base1)) {
dev_err(dev, "ioremap failed for page-1 resource\n"); return PTR_ERR(cspmu->base1);
}
}
if (supports_cycle_counter(cspmu)) { /* * The last logical counter is mapped to cycle counter if * there is a gap between regular and cycle counter. Otherwise, * logical and physical have 1-to-1 mapping.
*/
cspmu->cycle_counter_logical_idx =
(cspmu->num_logical_ctrs <= ARM_CSPMU_CYCLE_CNTR_IDX) ?
cspmu->num_logical_ctrs - 1 :
ARM_CSPMU_CYCLE_CNTR_IDX;
}
/* Skip IRQ request if the PMU does not support overflow interrupt. */
irq = platform_get_irq_optional(pdev, 0); if (irq < 0) return irq == -ENXIO ? 0 : irq;
ret = devm_request_irq(dev, irq, arm_cspmu_handle_irq,
IRQF_NOBALANCING | IRQF_NO_THREAD, dev_name(dev),
cspmu); if (ret) {
dev_err(dev, "Could not request IRQ %d\n", irq); return ret;
}
of_for_each_phandle(&it, ret, dev_of_node(cspmu->dev), "cpus", NULL, 0) {
cpu = of_cpu_node_to_id(it.node); if (cpu < 0) continue;
cpumask_set_cpu(cpu, &cspmu->associated_cpus);
} return ret == -ENOENT ? 0 : ret;
}
staticint arm_cspmu_get_cpus(struct arm_cspmu *cspmu)
{ int ret = 0;
if (arm_cspmu_apmt_node(cspmu->dev))
ret = arm_cspmu_acpi_get_cpus(cspmu); elseif (device_property_present(cspmu->dev, "cpus"))
ret = arm_cspmu_of_get_cpus(cspmu); else
cpumask_copy(&cspmu->associated_cpus, cpu_possible_mask);
if (!ret && cpumask_empty(&cspmu->associated_cpus)) {
dev_dbg(cspmu->dev, "No cpu associated with the PMU\n");
ret = -ENODEV;
} return ret;
}
staticint arm_cspmu_register_pmu(struct arm_cspmu *cspmu)
{ int ret, capabilities;
ret = arm_cspmu_alloc_attr_groups(cspmu); if (ret) return ret;
ret = cpuhp_state_add_instance(arm_cspmu_cpuhp_state,
&cspmu->cpuhp_node); if (ret) return ret;
capabilities = PERF_PMU_CAP_NO_EXCLUDE; if (cspmu->irq == 0)
capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
/* Nothing to do if this CPU doesn't own the PMU */ if (!cpumask_test_and_clear_cpu(cpu, &cspmu->active_cpu)) return 0;
/* Choose a new CPU to migrate ownership of the PMU to */
dst = cpumask_any_and_but(&cspmu->associated_cpus,
cpu_online_mask, cpu); if (dst >= nr_cpu_ids) return 0;
/* Use this CPU for event counting */
perf_pmu_migrate_context(&cspmu->pmu, cpu, dst);
arm_cspmu_set_active_cpu(dst, cspmu);
return 0;
}
staticint __init arm_cspmu_init(void)
{ int ret;
ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "perf/arm/cspmu:online",
arm_cspmu_cpu_online,
arm_cspmu_cpu_teardown); if (ret < 0) return ret;
arm_cspmu_cpuhp_state = ret; return platform_driver_register(&arm_cspmu_driver);
}
int arm_cspmu_impl_register(conststruct arm_cspmu_impl_match *impl_match)
{ struct arm_cspmu_impl_match *match; int ret = 0;
match = arm_cspmu_impl_match_get(impl_match->pmiidr_val);
if (match) {
mutex_lock(&arm_cspmu_lock);
if (!match->impl_init_ops) {
match->module = impl_match->module;
match->impl_init_ops = impl_match->impl_init_ops;
} else { /* Broken match table may contain non-unique entries */
WARN(1, "arm_cspmu backend already registered for module: %s, pmiidr: 0x%x, mask: 0x%x\n",
match->module_name,
match->pmiidr_val,
match->pmiidr_mask);
ret = -EINVAL;
}
mutex_unlock(&arm_cspmu_lock);
if (!ret)
ret = driver_attach(&arm_cspmu_driver.driver);
} else {
pr_err("arm_cspmu reg failed, unable to find a match for pmiidr: 0x%x\n",
impl_match->pmiidr_val);
match = arm_cspmu_impl_match_get(impl_match->pmiidr_val);
if (WARN_ON(!match)) return;
/* Unbind the driver from all matching backend devices. */ while ((dev = driver_find_device(&arm_cspmu_driver.driver, NULL,
match, arm_cspmu_match_device)))
device_release_driver(dev);
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.