18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * platform_device probing code for ARM performance counters.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2009 picoChip Designs, Ltd., Jamie Iles
68c2ecf20Sopenharmony_ci * Copyright (C) 2010 ARM Ltd., Will Deacon <will.deacon@arm.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "hw perfevents: " fmt
98c2ecf20Sopenharmony_ci#define dev_fmt pr_fmt
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/bug.h>
128c2ecf20Sopenharmony_ci#include <linux/cpumask.h>
138c2ecf20Sopenharmony_ci#include <linux/device.h>
148c2ecf20Sopenharmony_ci#include <linux/errno.h>
158c2ecf20Sopenharmony_ci#include <linux/irq.h>
168c2ecf20Sopenharmony_ci#include <linux/irqdesc.h>
178c2ecf20Sopenharmony_ci#include <linux/kconfig.h>
188c2ecf20Sopenharmony_ci#include <linux/of.h>
198c2ecf20Sopenharmony_ci#include <linux/of_device.h>
208c2ecf20Sopenharmony_ci#include <linux/percpu.h>
218c2ecf20Sopenharmony_ci#include <linux/perf/arm_pmu.h>
228c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
238c2ecf20Sopenharmony_ci#include <linux/printk.h>
248c2ecf20Sopenharmony_ci#include <linux/smp.h>
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic int probe_current_pmu(struct arm_pmu *pmu,
278c2ecf20Sopenharmony_ci			     const struct pmu_probe_info *info)
288c2ecf20Sopenharmony_ci{
298c2ecf20Sopenharmony_ci	int cpu = get_cpu();
308c2ecf20Sopenharmony_ci	unsigned int cpuid = read_cpuid_id();
318c2ecf20Sopenharmony_ci	int ret = -ENODEV;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	pr_info("probing PMU on CPU %d\n", cpu);
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	for (; info->init != NULL; info++) {
368c2ecf20Sopenharmony_ci		if ((cpuid & info->mask) != info->cpuid)
378c2ecf20Sopenharmony_ci			continue;
388c2ecf20Sopenharmony_ci		ret = info->init(pmu);
398c2ecf20Sopenharmony_ci		break;
408c2ecf20Sopenharmony_ci	}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	put_cpu();
438c2ecf20Sopenharmony_ci	return ret;
448c2ecf20Sopenharmony_ci}
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic int pmu_parse_percpu_irq(struct arm_pmu *pmu, int irq)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	int cpu, ret;
498c2ecf20Sopenharmony_ci	struct pmu_hw_events __percpu *hw_events = pmu->hw_events;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	ret = irq_get_percpu_devid_partition(irq, &pmu->supported_cpus);
528c2ecf20Sopenharmony_ci	if (ret)
538c2ecf20Sopenharmony_ci		return ret;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	for_each_cpu(cpu, &pmu->supported_cpus)
568c2ecf20Sopenharmony_ci		per_cpu(hw_events->irq, cpu) = irq;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	return 0;
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic bool pmu_has_irq_affinity(struct device_node *node)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	return !!of_find_property(node, "interrupt-affinity", NULL);
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic int pmu_parse_irq_affinity(struct device_node *node, int i)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	struct device_node *dn;
698c2ecf20Sopenharmony_ci	int cpu;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	/*
728c2ecf20Sopenharmony_ci	 * If we don't have an interrupt-affinity property, we guess irq
738c2ecf20Sopenharmony_ci	 * affinity matches our logical CPU order, as we used to assume.
748c2ecf20Sopenharmony_ci	 * This is fragile, so we'll warn in pmu_parse_irqs().
758c2ecf20Sopenharmony_ci	 */
768c2ecf20Sopenharmony_ci	if (!pmu_has_irq_affinity(node))
778c2ecf20Sopenharmony_ci		return i;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	dn = of_parse_phandle(node, "interrupt-affinity", i);
808c2ecf20Sopenharmony_ci	if (!dn) {
818c2ecf20Sopenharmony_ci		pr_warn("failed to parse interrupt-affinity[%d] for %pOFn\n",
828c2ecf20Sopenharmony_ci			i, node);
838c2ecf20Sopenharmony_ci		return -EINVAL;
848c2ecf20Sopenharmony_ci	}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	cpu = of_cpu_node_to_id(dn);
878c2ecf20Sopenharmony_ci	if (cpu < 0) {
888c2ecf20Sopenharmony_ci		pr_warn("failed to find logical CPU for %pOFn\n", dn);
898c2ecf20Sopenharmony_ci		cpu = nr_cpu_ids;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	of_node_put(dn);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	return cpu;
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistatic int pmu_parse_irqs(struct arm_pmu *pmu)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	int i = 0, num_irqs;
1008c2ecf20Sopenharmony_ci	struct platform_device *pdev = pmu->plat_device;
1018c2ecf20Sopenharmony_ci	struct pmu_hw_events __percpu *hw_events = pmu->hw_events;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	num_irqs = platform_irq_count(pdev);
1048c2ecf20Sopenharmony_ci	if (num_irqs < 0)
1058c2ecf20Sopenharmony_ci		return dev_err_probe(&pdev->dev, num_irqs, "unable to count PMU IRQs\n");
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	/*
1088c2ecf20Sopenharmony_ci	 * In this case we have no idea which CPUs are covered by the PMU.
1098c2ecf20Sopenharmony_ci	 * To match our prior behaviour, we assume all CPUs in this case.
1108c2ecf20Sopenharmony_ci	 */
1118c2ecf20Sopenharmony_ci	if (num_irqs == 0) {
1128c2ecf20Sopenharmony_ci		pr_warn("no irqs for PMU, sampling events not supported\n");
1138c2ecf20Sopenharmony_ci		pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
1148c2ecf20Sopenharmony_ci		cpumask_setall(&pmu->supported_cpus);
1158c2ecf20Sopenharmony_ci		return 0;
1168c2ecf20Sopenharmony_ci	}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	if (num_irqs == 1) {
1198c2ecf20Sopenharmony_ci		int irq = platform_get_irq(pdev, 0);
1208c2ecf20Sopenharmony_ci		if ((irq > 0) && irq_is_percpu_devid(irq))
1218c2ecf20Sopenharmony_ci			return pmu_parse_percpu_irq(pmu, irq);
1228c2ecf20Sopenharmony_ci	}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (nr_cpu_ids != 1 && !pmu_has_irq_affinity(pdev->dev.of_node)) {
1258c2ecf20Sopenharmony_ci		pr_warn("no interrupt-affinity property for %pOF, guessing.\n",
1268c2ecf20Sopenharmony_ci			pdev->dev.of_node);
1278c2ecf20Sopenharmony_ci	}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	for (i = 0; i < num_irqs; i++) {
1308c2ecf20Sopenharmony_ci		int cpu, irq;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci		irq = platform_get_irq(pdev, i);
1338c2ecf20Sopenharmony_ci		if (WARN_ON(irq <= 0))
1348c2ecf20Sopenharmony_ci			continue;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci		if (irq_is_percpu_devid(irq)) {
1378c2ecf20Sopenharmony_ci			pr_warn("multiple PPIs or mismatched SPI/PPI detected\n");
1388c2ecf20Sopenharmony_ci			return -EINVAL;
1398c2ecf20Sopenharmony_ci		}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci		cpu = pmu_parse_irq_affinity(pdev->dev.of_node, i);
1428c2ecf20Sopenharmony_ci		if (cpu < 0)
1438c2ecf20Sopenharmony_ci			return cpu;
1448c2ecf20Sopenharmony_ci		if (cpu >= nr_cpu_ids)
1458c2ecf20Sopenharmony_ci			continue;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci		if (per_cpu(hw_events->irq, cpu)) {
1488c2ecf20Sopenharmony_ci			pr_warn("multiple PMU IRQs for the same CPU detected\n");
1498c2ecf20Sopenharmony_ci			return -EINVAL;
1508c2ecf20Sopenharmony_ci		}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci		per_cpu(hw_events->irq, cpu) = irq;
1538c2ecf20Sopenharmony_ci		cpumask_set_cpu(cpu, &pmu->supported_cpus);
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	return 0;
1578c2ecf20Sopenharmony_ci}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_cistatic int armpmu_request_irqs(struct arm_pmu *armpmu)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	struct pmu_hw_events __percpu *hw_events = armpmu->hw_events;
1628c2ecf20Sopenharmony_ci	int cpu, err = 0;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	for_each_cpu(cpu, &armpmu->supported_cpus) {
1658c2ecf20Sopenharmony_ci		int irq = per_cpu(hw_events->irq, cpu);
1668c2ecf20Sopenharmony_ci		if (!irq)
1678c2ecf20Sopenharmony_ci			continue;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci		err = armpmu_request_irq(irq, cpu);
1708c2ecf20Sopenharmony_ci		if (err)
1718c2ecf20Sopenharmony_ci			break;
1728c2ecf20Sopenharmony_ci	}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	return err;
1758c2ecf20Sopenharmony_ci}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_cistatic void armpmu_free_irqs(struct arm_pmu *armpmu)
1788c2ecf20Sopenharmony_ci{
1798c2ecf20Sopenharmony_ci	int cpu;
1808c2ecf20Sopenharmony_ci	struct pmu_hw_events __percpu *hw_events = armpmu->hw_events;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	for_each_cpu(cpu, &armpmu->supported_cpus) {
1838c2ecf20Sopenharmony_ci		int irq = per_cpu(hw_events->irq, cpu);
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci		armpmu_free_irq(irq, cpu);
1868c2ecf20Sopenharmony_ci	}
1878c2ecf20Sopenharmony_ci}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ciint arm_pmu_device_probe(struct platform_device *pdev,
1908c2ecf20Sopenharmony_ci			 const struct of_device_id *of_table,
1918c2ecf20Sopenharmony_ci			 const struct pmu_probe_info *probe_table)
1928c2ecf20Sopenharmony_ci{
1938c2ecf20Sopenharmony_ci	const struct of_device_id *of_id;
1948c2ecf20Sopenharmony_ci	armpmu_init_fn init_fn;
1958c2ecf20Sopenharmony_ci	struct device_node *node = pdev->dev.of_node;
1968c2ecf20Sopenharmony_ci	struct arm_pmu *pmu;
1978c2ecf20Sopenharmony_ci	int ret = -ENODEV;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	pmu = armpmu_alloc();
2008c2ecf20Sopenharmony_ci	if (!pmu)
2018c2ecf20Sopenharmony_ci		return -ENOMEM;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	pmu->plat_device = pdev;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	ret = pmu_parse_irqs(pmu);
2068c2ecf20Sopenharmony_ci	if (ret)
2078c2ecf20Sopenharmony_ci		goto out_free;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	if (node && (of_id = of_match_node(of_table, pdev->dev.of_node))) {
2108c2ecf20Sopenharmony_ci		init_fn = of_id->data;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci		pmu->secure_access = of_property_read_bool(pdev->dev.of_node,
2138c2ecf20Sopenharmony_ci							   "secure-reg-access");
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci		/* arm64 systems boot only as non-secure */
2168c2ecf20Sopenharmony_ci		if (IS_ENABLED(CONFIG_ARM64) && pmu->secure_access) {
2178c2ecf20Sopenharmony_ci			pr_warn("ignoring \"secure-reg-access\" property for arm64\n");
2188c2ecf20Sopenharmony_ci			pmu->secure_access = false;
2198c2ecf20Sopenharmony_ci		}
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci		ret = init_fn(pmu);
2228c2ecf20Sopenharmony_ci	} else if (probe_table) {
2238c2ecf20Sopenharmony_ci		cpumask_setall(&pmu->supported_cpus);
2248c2ecf20Sopenharmony_ci		ret = probe_current_pmu(pmu, probe_table);
2258c2ecf20Sopenharmony_ci	}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	if (ret) {
2288c2ecf20Sopenharmony_ci		pr_info("%pOF: failed to probe PMU!\n", node);
2298c2ecf20Sopenharmony_ci		goto out_free;
2308c2ecf20Sopenharmony_ci	}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	ret = armpmu_request_irqs(pmu);
2338c2ecf20Sopenharmony_ci	if (ret)
2348c2ecf20Sopenharmony_ci		goto out_free_irqs;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	ret = armpmu_register(pmu);
2378c2ecf20Sopenharmony_ci	if (ret)
2388c2ecf20Sopenharmony_ci		goto out_free_irqs;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	return 0;
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ciout_free_irqs:
2438c2ecf20Sopenharmony_ci	armpmu_free_irqs(pmu);
2448c2ecf20Sopenharmony_ciout_free:
2458c2ecf20Sopenharmony_ci	pr_info("%pOF: failed to register PMU devices!\n", node);
2468c2ecf20Sopenharmony_ci	armpmu_free(pmu);
2478c2ecf20Sopenharmony_ci	return ret;
2488c2ecf20Sopenharmony_ci}
249