]>
Commit | Line | Data |
---|---|---|
18bfcfe5 MR |
1 | /* |
2 | * platform_device probing code for ARM performance counters. | |
3 | * | |
4 | * Copyright (C) 2009 picoChip Designs, Ltd., Jamie Iles | |
5 | * Copyright (C) 2010 ARM Ltd., Will Deacon <[email protected]> | |
6 | */ | |
7 | #define pr_fmt(fmt) "hw perfevents: " fmt | |
8 | ||
9 | #include <linux/bug.h> | |
10 | #include <linux/cpumask.h> | |
11 | #include <linux/device.h> | |
12 | #include <linux/errno.h> | |
13 | #include <linux/irq.h> | |
14 | #include <linux/irqdesc.h> | |
15 | #include <linux/kconfig.h> | |
16 | #include <linux/of.h> | |
17 | #include <linux/of_device.h> | |
18 | #include <linux/percpu.h> | |
19 | #include <linux/perf/arm_pmu.h> | |
20 | #include <linux/platform_device.h> | |
21 | #include <linux/printk.h> | |
22 | #include <linux/smp.h> | |
23 | ||
24 | static int probe_current_pmu(struct arm_pmu *pmu, | |
25 | const struct pmu_probe_info *info) | |
26 | { | |
27 | int cpu = get_cpu(); | |
28 | unsigned int cpuid = read_cpuid_id(); | |
29 | int ret = -ENODEV; | |
30 | ||
31 | pr_info("probing PMU on CPU %d\n", cpu); | |
32 | ||
33 | for (; info->init != NULL; info++) { | |
34 | if ((cpuid & info->mask) != info->cpuid) | |
35 | continue; | |
36 | ret = info->init(pmu); | |
37 | break; | |
38 | } | |
39 | ||
40 | put_cpu(); | |
41 | return ret; | |
42 | } | |
43 | ||
44 | static int pmu_parse_percpu_irq(struct arm_pmu *pmu, int irq) | |
45 | { | |
46 | int cpu, ret; | |
47 | struct pmu_hw_events __percpu *hw_events = pmu->hw_events; | |
48 | ||
49 | ret = irq_get_percpu_devid_partition(irq, &pmu->supported_cpus); | |
50 | if (ret) | |
51 | return ret; | |
52 | ||
53 | for_each_cpu(cpu, &pmu->supported_cpus) | |
54 | per_cpu(hw_events->irq, cpu) = irq; | |
55 | ||
56 | return 0; | |
57 | } | |
58 | ||
59 | static bool pmu_has_irq_affinity(struct device_node *node) | |
60 | { | |
61 | return !!of_find_property(node, "interrupt-affinity", NULL); | |
62 | } | |
63 | ||
64 | static int pmu_parse_irq_affinity(struct device_node *node, int i) | |
65 | { | |
66 | struct device_node *dn; | |
67 | int cpu; | |
68 | ||
69 | /* | |
70 | * If we don't have an interrupt-affinity property, we guess irq | |
71 | * affinity matches our logical CPU order, as we used to assume. | |
72 | * This is fragile, so we'll warn in pmu_parse_irqs(). | |
73 | */ | |
74 | if (!pmu_has_irq_affinity(node)) | |
75 | return i; | |
76 | ||
77 | dn = of_parse_phandle(node, "interrupt-affinity", i); | |
78 | if (!dn) { | |
79 | pr_warn("failed to parse interrupt-affinity[%d] for %s\n", | |
80 | i, node->name); | |
81 | return -EINVAL; | |
82 | } | |
83 | ||
84 | /* Now look up the logical CPU number */ | |
85 | for_each_possible_cpu(cpu) { | |
86 | struct device_node *cpu_dn; | |
87 | ||
88 | cpu_dn = of_cpu_device_node_get(cpu); | |
89 | of_node_put(cpu_dn); | |
90 | ||
91 | if (dn == cpu_dn) | |
92 | break; | |
93 | } | |
94 | ||
95 | if (cpu >= nr_cpu_ids) { | |
96 | pr_warn("failed to find logical CPU for %s\n", dn->name); | |
97 | } | |
98 | ||
99 | of_node_put(dn); | |
100 | ||
101 | return cpu; | |
102 | } | |
103 | ||
104 | static int pmu_parse_irqs(struct arm_pmu *pmu) | |
105 | { | |
106 | int i = 0, num_irqs; | |
107 | struct platform_device *pdev = pmu->plat_device; | |
108 | struct pmu_hw_events __percpu *hw_events = pmu->hw_events; | |
109 | ||
110 | num_irqs = platform_irq_count(pdev); | |
111 | if (num_irqs < 0) { | |
112 | pr_err("unable to count PMU IRQs\n"); | |
113 | return num_irqs; | |
114 | } | |
115 | ||
116 | /* | |
117 | * In this case we have no idea which CPUs are covered by the PMU. | |
118 | * To match our prior behaviour, we assume all CPUs in this case. | |
119 | */ | |
120 | if (num_irqs == 0) { | |
121 | pr_warn("no irqs for PMU, sampling events not supported\n"); | |
122 | pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT; | |
123 | cpumask_setall(&pmu->supported_cpus); | |
124 | return 0; | |
125 | } | |
126 | ||
127 | if (num_irqs == 1) { | |
128 | int irq = platform_get_irq(pdev, 0); | |
129 | if (irq && irq_is_percpu(irq)) | |
130 | return pmu_parse_percpu_irq(pmu, irq); | |
131 | } | |
132 | ||
133 | if (!pmu_has_irq_affinity(pdev->dev.of_node)) { | |
134 | pr_warn("no interrupt-affinity property for %s, guessing.\n", | |
135 | of_node_full_name(pdev->dev.of_node)); | |
136 | } | |
137 | ||
138 | /* | |
139 | * Some platforms have all PMU IRQs OR'd into a single IRQ, with a | |
140 | * special platdata function that attempts to demux them. | |
141 | */ | |
142 | if (dev_get_platdata(&pdev->dev)) | |
143 | cpumask_setall(&pmu->supported_cpus); | |
144 | ||
145 | for (i = 0; i < num_irqs; i++) { | |
146 | int cpu, irq; | |
147 | ||
148 | irq = platform_get_irq(pdev, i); | |
149 | if (WARN_ON(irq <= 0)) | |
150 | continue; | |
151 | ||
152 | if (irq_is_percpu(irq)) { | |
153 | pr_warn("multiple PPIs or mismatched SPI/PPI detected\n"); | |
154 | return -EINVAL; | |
155 | } | |
156 | ||
157 | cpu = pmu_parse_irq_affinity(pdev->dev.of_node, i); | |
158 | if (cpu < 0) | |
159 | return cpu; | |
160 | if (cpu >= nr_cpu_ids) | |
161 | continue; | |
162 | ||
163 | if (per_cpu(hw_events->irq, cpu)) { | |
164 | pr_warn("multiple PMU IRQs for the same CPU detected\n"); | |
165 | return -EINVAL; | |
166 | } | |
167 | ||
168 | per_cpu(hw_events->irq, cpu) = irq; | |
169 | cpumask_set_cpu(cpu, &pmu->supported_cpus); | |
170 | } | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | int arm_pmu_device_probe(struct platform_device *pdev, | |
176 | const struct of_device_id *of_table, | |
177 | const struct pmu_probe_info *probe_table) | |
178 | { | |
179 | const struct of_device_id *of_id; | |
180 | armpmu_init_fn init_fn; | |
181 | struct device_node *node = pdev->dev.of_node; | |
182 | struct arm_pmu *pmu; | |
183 | int ret = -ENODEV; | |
184 | ||
185 | pmu = armpmu_alloc(); | |
186 | if (!pmu) | |
187 | return -ENOMEM; | |
188 | ||
189 | pmu->plat_device = pdev; | |
190 | ||
191 | ret = pmu_parse_irqs(pmu); | |
192 | if (ret) | |
193 | goto out_free; | |
194 | ||
195 | if (node && (of_id = of_match_node(of_table, pdev->dev.of_node))) { | |
196 | init_fn = of_id->data; | |
197 | ||
198 | pmu->secure_access = of_property_read_bool(pdev->dev.of_node, | |
199 | "secure-reg-access"); | |
200 | ||
201 | /* arm64 systems boot only as non-secure */ | |
202 | if (IS_ENABLED(CONFIG_ARM64) && pmu->secure_access) { | |
203 | pr_warn("ignoring \"secure-reg-access\" property for arm64\n"); | |
204 | pmu->secure_access = false; | |
205 | } | |
206 | ||
207 | ret = init_fn(pmu); | |
208 | } else if (probe_table) { | |
209 | cpumask_setall(&pmu->supported_cpus); | |
210 | ret = probe_current_pmu(pmu, probe_table); | |
211 | } | |
212 | ||
213 | if (ret) { | |
214 | pr_info("%s: failed to probe PMU!\n", of_node_full_name(node)); | |
215 | goto out_free; | |
216 | } | |
217 | ||
218 | ret = armpmu_request_irqs(pmu); | |
219 | if (ret) | |
220 | goto out_free_irqs; | |
221 | ||
222 | ret = armpmu_register(pmu); | |
223 | if (ret) | |
224 | goto out_free; | |
225 | ||
226 | return 0; | |
227 | ||
228 | out_free_irqs: | |
229 | armpmu_free_irqs(pmu); | |
230 | out_free: | |
231 | pr_info("%s: failed to register PMU devices!\n", | |
232 | of_node_full_name(node)); | |
233 | armpmu_free(pmu); | |
234 | return ret; | |
235 | } |