]>
Commit | Line | Data |
---|---|---|
5ba3f43e | 1 | /* |
ea3f0419 | 2 | * Copyright (c) 2017-2020 Apple Inc. All rights reserved. |
5ba3f43e A |
3 | * |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
14 | * | |
15 | * Please obtain a copy of the License at | |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
25 | * | |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ | |
27 | */ | |
28 | ||
29 | #include <arm/cpu_data_internal.h> | |
30 | #include <arm/machine_routines.h> | |
31 | #include <arm64/monotonic.h> | |
d9a64523 | 32 | #include <kern/assert.h> |
5ba3f43e | 33 | #include <kern/debug.h> /* panic */ |
ea3f0419 | 34 | #include <kern/kpc.h> |
5ba3f43e | 35 | #include <kern/monotonic.h> |
0a7de745 | 36 | #include <machine/atomic.h> |
5ba3f43e | 37 | #include <machine/limits.h> /* CHAR_BIT */ |
0a7de745 A |
38 | #include <os/overflow.h> |
39 | #include <pexpert/arm64/board_config.h> | |
40 | #include <pexpert/device_tree.h> /* DTFindEntry */ | |
41 | #include <pexpert/pexpert.h> | |
5ba3f43e A |
42 | #include <stdatomic.h> |
43 | #include <stdint.h> | |
44 | #include <string.h> | |
45 | #include <sys/errno.h> | |
46 | #include <sys/monotonic.h> | |
0a7de745 A |
47 | |
48 | /* | |
49 | * Ensure that control registers read back what was written under MACH_ASSERT | |
50 | * kernels. | |
51 | * | |
52 | * A static inline function cannot be used due to passing the register through | |
53 | * the builtin -- it requires a constant string as its first argument, since | |
54 | * MSRs registers are encoded as an immediate in the instruction. | |
55 | */ | |
56 | #if MACH_ASSERT | |
57 | #define CTRL_REG_SET(reg, val) do { \ | |
58 | __builtin_arm_wsr64((reg), (val)); \ | |
59 | uint64_t __check_reg = __builtin_arm_rsr64((reg)); \ | |
60 | if (__check_reg != (val)) { \ | |
61 | panic("value written to %s was not read back (wrote %llx, read %llx)", \ | |
62 | #reg, (val), __check_reg); \ | |
63 | } \ | |
64 | } while (0) | |
65 | #else /* MACH_ASSERT */ | |
66 | #define CTRL_REG_SET(reg, val) __builtin_arm_wsr64((reg), (val)) | |
67 | #endif /* MACH_ASSERT */ | |
5ba3f43e A |
68 | |
69 | #pragma mark core counters | |
70 | ||
71 | bool mt_core_supported = true; | |
5ba3f43e A |
72 | |
73 | /* | |
74 | * PMC[0-1] are the 48-bit fixed counters -- PMC0 is cycles and PMC1 is | |
75 | * instructions (see arm64/monotonic.h). | |
76 | * | |
77 | * PMC2+ are currently handled by kpc. | |
78 | */ | |
79 | ||
80 | #define PMC0 "s3_2_c15_c0_0" | |
81 | #define PMC1 "s3_2_c15_c1_0" | |
82 | #define PMC2 "s3_2_c15_c2_0" | |
83 | #define PMC3 "s3_2_c15_c3_0" | |
84 | #define PMC4 "s3_2_c15_c4_0" | |
85 | #define PMC5 "s3_2_c15_c5_0" | |
86 | #define PMC6 "s3_2_c15_c6_0" | |
87 | #define PMC7 "s3_2_c15_c7_0" | |
ea3f0419 A |
88 | |
89 | #define PMC_0_7(X, A) X(0, A); X(1, A); X(2, A); X(3, A); X(4, A); X(5, A); \ | |
90 | X(6, A); X(7, A) | |
91 | ||
92 | #if CORE_NCTRS > 8 | |
5ba3f43e A |
93 | #define PMC8 "s3_2_c15_c9_0" |
94 | #define PMC9 "s3_2_c15_c10_0" | |
ea3f0419 A |
95 | #define PMC_8_9(X, A) X(8, A); X(9, A) |
96 | #else // CORE_NCTRS > 8 | |
97 | #define PMC_8_9(X, A) | |
98 | #endif // CORE_NCTRS > 8 | |
99 | ||
100 | #define PMC_ALL(X, A) PMC_0_7(X, A); PMC_8_9(X, A) | |
5ba3f43e | 101 | |
d9a64523 A |
102 | #define CTR_MAX ((UINT64_C(1) << 47) - 1) |
103 | ||
5ba3f43e A |
104 | #define CYCLES 0 |
105 | #define INSTRS 1 | |
106 | ||
107 | /* | |
108 | * PMC0's offset into a core's PIO range. | |
109 | * | |
110 | * This allows cores to remotely query another core's counters. | |
111 | */ | |
112 | ||
113 | #define PIO_PMC0_OFFSET (0x200) | |
114 | ||
115 | /* | |
116 | * The offset of the counter in the configuration registers. Post-Hurricane | |
117 | * devices have additional counters that need a larger shift than the original | |
118 | * counters. | |
119 | * | |
120 | * XXX For now, just support the lower-numbered counters. | |
121 | */ | |
122 | #define CTR_POS(CTR) (CTR) | |
123 | ||
124 | /* | |
125 | * PMCR0 is the main control register for the performance monitor. It | |
126 | * controls whether the counters are enabled, how they deliver interrupts, and | |
127 | * other features. | |
128 | */ | |
129 | ||
5ba3f43e A |
130 | #define PMCR0_CTR_EN(CTR) (UINT64_C(1) << CTR_POS(CTR)) |
131 | #define PMCR0_FIXED_EN (PMCR0_CTR_EN(CYCLES) | PMCR0_CTR_EN(INSTRS)) | |
132 | /* how interrupts are delivered on a PMI */ | |
133 | enum { | |
134 | PMCR0_INTGEN_OFF = 0, | |
135 | PMCR0_INTGEN_PMI = 1, | |
136 | PMCR0_INTGEN_AIC = 2, | |
137 | PMCR0_INTGEN_HALT = 3, | |
138 | PMCR0_INTGEN_FIQ = 4, | |
139 | }; | |
ea3f0419 | 140 | #define PMCR0_INTGEN_SET(X) ((uint64_t)(X) << 8) |
0a7de745 A |
141 | |
142 | #if CPMU_AIC_PMI | |
143 | #define PMCR0_INTGEN_INIT PMCR0_INTGEN_SET(PMCR0_INTGEN_AIC) | |
144 | #else /* CPMU_AIC_PMI */ | |
d9a64523 | 145 | #define PMCR0_INTGEN_INIT PMCR0_INTGEN_SET(PMCR0_INTGEN_FIQ) |
0a7de745 A |
146 | #endif /* !CPMU_AIC_PMI */ |
147 | ||
ea3f0419 A |
148 | #define PMCR0_PMI_SHIFT (12) |
149 | #define PMCR0_CTR_GE8_PMI_SHIFT (44) | |
150 | #define PMCR0_PMI_EN(CTR) (UINT64_C(1) << (PMCR0_PMI_SHIFT + CTR_POS(CTR))) | |
d9a64523 | 151 | /* fixed counters are always counting */ |
5ba3f43e | 152 | #define PMCR0_PMI_INIT (PMCR0_PMI_EN(CYCLES) | PMCR0_PMI_EN(INSTRS)) |
d9a64523 | 153 | /* disable counting on a PMI */ |
5ba3f43e A |
154 | #define PMCR0_DISCNT_EN (UINT64_C(1) << 20) |
155 | /* block PMIs until ERET retires */ | |
156 | #define PMCR0_WFRFE_EN (UINT64_C(1) << 22) | |
157 | /* count global (not just core-local) L2C events */ | |
158 | #define PMCR0_L2CGLOBAL_EN (UINT64_C(1) << 23) | |
159 | /* user mode access to configuration registers */ | |
160 | #define PMCR0_USEREN_EN (UINT64_C(1) << 30) | |
ea3f0419 | 161 | #define PMCR0_CTR_GE8_EN_SHIFT (32) |
5ba3f43e | 162 | |
ea3f0419 | 163 | #define PMCR0_INIT (PMCR0_INTGEN_INIT | PMCR0_PMI_INIT) |
5ba3f43e A |
164 | |
165 | /* | |
166 | * PMCR1 controls which execution modes count events. | |
167 | */ | |
168 | ||
169 | #define PMCR1 "s3_1_c15_c1_0" | |
170 | ||
171 | #define PMCR1_EL0A32_EN(CTR) (UINT64_C(1) << (0 + CTR_POS(CTR))) | |
172 | #define PMCR1_EL0A64_EN(CTR) (UINT64_C(1) << (8 + CTR_POS(CTR))) | |
173 | #define PMCR1_EL1A64_EN(CTR) (UINT64_C(1) << (16 + CTR_POS(CTR))) | |
174 | /* PMCR1_EL3A64 is not supported on systems with no monitor */ | |
175 | #if defined(APPLEHURRICANE) | |
176 | #define PMCR1_EL3A64_EN(CTR) UINT64_C(0) | |
177 | #else | |
178 | #define PMCR1_EL3A64_EN(CTR) (UINT64_C(1) << (24 + CTR_POS(CTR))) | |
179 | #endif | |
180 | #define PMCR1_ALL_EN(CTR) (PMCR1_EL0A32_EN(CTR) | PMCR1_EL0A64_EN(CTR) | \ | |
0a7de745 | 181 | PMCR1_EL1A64_EN(CTR) | PMCR1_EL3A64_EN(CTR)) |
5ba3f43e A |
182 | |
183 | /* fixed counters always count in all modes */ | |
184 | #define PMCR1_INIT (PMCR1_ALL_EN(CYCLES) | PMCR1_ALL_EN(INSTRS)) | |
185 | ||
186 | static inline void | |
187 | core_init_execution_modes(void) | |
188 | { | |
189 | uint64_t pmcr1; | |
190 | ||
191 | pmcr1 = __builtin_arm_rsr64(PMCR1); | |
192 | pmcr1 |= PMCR1_INIT; | |
193 | __builtin_arm_wsr64(PMCR1, pmcr1); | |
194 | } | |
195 | ||
5ba3f43e A |
196 | /* |
197 | * PMCR2 controls watchpoint registers. | |
198 | * | |
199 | * PMCR3 controls breakpoints and address matching. | |
200 | * | |
201 | * PMCR4 controls opcode matching. | |
202 | */ | |
203 | ||
204 | #define PMCR2 "s3_1_c15_c2_0" | |
205 | #define PMCR3 "s3_1_c15_c3_0" | |
206 | #define PMCR4 "s3_1_c15_c4_0" | |
207 | ||
0a7de745 | 208 | #define PMSR "s3_1_c15_c13_0" |
5ba3f43e | 209 | |
0a7de745 | 210 | #define PMSR_OVF(CTR) (1ULL << (CTR)) |
5ba3f43e | 211 | |
ea3f0419 A |
212 | #define PMESR0 "S3_1_c15_c5_0" |
213 | #define PMESR1 "S3_1_c15_c6_0" | |
214 | ||
5ba3f43e | 215 | static int |
d9a64523 | 216 | core_init(__unused mt_device_t dev) |
5ba3f43e A |
217 | { |
218 | /* the dev node interface to the core counters is still unsupported */ | |
219 | return ENOTSUP; | |
220 | } | |
221 | ||
222 | struct mt_cpu * | |
223 | mt_cur_cpu(void) | |
224 | { | |
225 | return &getCpuDatap()->cpu_monotonic; | |
226 | } | |
227 | ||
228 | uint64_t | |
229 | mt_core_snap(unsigned int ctr) | |
230 | { | |
231 | switch (ctr) { | |
ea3f0419 A |
232 | #define PMC_RD(CTR, UNUSED) case (CTR): return __builtin_arm_rsr64(PMC ## CTR) |
233 | PMC_ALL(PMC_RD, 0); | |
234 | #undef PMC_RD | |
5ba3f43e A |
235 | default: |
236 | panic("monotonic: invalid core counter read: %u", ctr); | |
d9a64523 | 237 | __builtin_unreachable(); |
5ba3f43e A |
238 | } |
239 | } | |
240 | ||
241 | void | |
242 | mt_core_set_snap(unsigned int ctr, uint64_t count) | |
243 | { | |
244 | switch (ctr) { | |
245 | case 0: | |
246 | __builtin_arm_wsr64(PMC0, count); | |
247 | break; | |
248 | case 1: | |
249 | __builtin_arm_wsr64(PMC1, count); | |
250 | break; | |
251 | default: | |
252 | panic("monotonic: invalid core counter %u write %llu", ctr, count); | |
d9a64523 | 253 | __builtin_unreachable(); |
5ba3f43e A |
254 | } |
255 | } | |
256 | ||
257 | static void | |
258 | core_set_enabled(void) | |
259 | { | |
0a7de745 | 260 | uint64_t pmcr0 = __builtin_arm_rsr64(PMCR0); |
5ba3f43e | 261 | pmcr0 |= PMCR0_INIT | PMCR0_FIXED_EN; |
ea3f0419 A |
262 | |
263 | if (kpc_get_running() & KPC_CLASS_CONFIGURABLE_MASK) { | |
264 | uint64_t kpc_ctrs = kpc_get_configurable_pmc_mask( | |
265 | KPC_CLASS_CONFIGURABLE_MASK) << MT_CORE_NFIXED; | |
266 | #if KPC_ARM64_CONFIGURABLE_COUNT > 6 | |
267 | uint64_t ctrs_ge8 = kpc_ctrs >> 8; | |
268 | pmcr0 |= ctrs_ge8 << PMCR0_CTR_GE8_EN_SHIFT; | |
269 | pmcr0 |= ctrs_ge8 << PMCR0_CTR_GE8_PMI_SHIFT; | |
270 | kpc_ctrs &= (1ULL << 8) - 1; | |
271 | #endif /* KPC_ARM64_CONFIGURABLE_COUNT > 6 */ | |
272 | kpc_ctrs |= kpc_ctrs << PMCR0_PMI_SHIFT; | |
273 | pmcr0 |= kpc_ctrs; | |
274 | } | |
275 | ||
5ba3f43e | 276 | __builtin_arm_wsr64(PMCR0, pmcr0); |
0a7de745 A |
277 | #if MACH_ASSERT |
278 | /* | |
279 | * Only check for the values that were ORed in. | |
280 | */ | |
281 | uint64_t pmcr0_check = __builtin_arm_rsr64(PMCR0); | |
ea3f0419 A |
282 | if ((pmcr0_check & (PMCR0_INIT | PMCR0_FIXED_EN)) != (PMCR0_INIT | PMCR0_FIXED_EN)) { |
283 | panic("monotonic: hardware ignored enable (read %llx, wrote %llx)", | |
284 | pmcr0_check, pmcr0); | |
0a7de745 A |
285 | } |
286 | #endif /* MACH_ASSERT */ | |
5ba3f43e A |
287 | } |
288 | ||
289 | static void | |
290 | core_idle(__unused cpu_data_t *cpu) | |
291 | { | |
292 | assert(cpu != NULL); | |
293 | assert(ml_get_interrupts_enabled() == FALSE); | |
294 | ||
295 | #if DEBUG | |
296 | uint64_t pmcr0 = __builtin_arm_rsr64(PMCR0); | |
297 | if ((pmcr0 & PMCR0_FIXED_EN) == 0) { | |
0a7de745 | 298 | panic("monotonic: counters disabled before idling, pmcr0 = 0x%llx\n", pmcr0); |
5ba3f43e A |
299 | } |
300 | uint64_t pmcr1 = __builtin_arm_rsr64(PMCR1); | |
301 | if ((pmcr1 & PMCR1_INIT) == 0) { | |
0a7de745 | 302 | panic("monotonic: counter modes disabled before idling, pmcr1 = 0x%llx\n", pmcr1); |
5ba3f43e A |
303 | } |
304 | #endif /* DEBUG */ | |
305 | ||
306 | /* disable counters before updating */ | |
307 | __builtin_arm_wsr64(PMCR0, PMCR0_INIT); | |
308 | ||
309 | mt_update_fixed_counts(); | |
310 | } | |
311 | ||
d9a64523 A |
312 | #pragma mark uncore performance monitor |
313 | ||
c6bf4f31 A |
314 | #if HAS_UNCORE_CTRS |
315 | ||
316 | static bool mt_uncore_initted = false; | |
317 | ||
318 | /* | |
319 | * Uncore Performance Monitor | |
320 | * | |
321 | * Uncore performance monitors provide event-counting for the last-level caches | |
322 | * (LLCs). Each LLC has its own uncore performance monitor, which can only be | |
323 | * accessed by cores that use that LLC. Like the core performance monitoring | |
324 | * unit, uncore counters are configured globally. If there is more than one | |
325 | * LLC on the system, PIO reads must be used to satisfy uncore requests (using | |
326 | * the `_r` remote variants of the access functions). Otherwise, local MSRs | |
327 | * suffice (using the `_l` local variants of the access functions). | |
328 | */ | |
329 | ||
330 | #if UNCORE_PER_CLUSTER | |
331 | static vm_size_t cpm_impl_size = 0; | |
332 | static uintptr_t cpm_impl[__ARM_CLUSTER_COUNT__] = {}; | |
333 | static uintptr_t cpm_impl_phys[__ARM_CLUSTER_COUNT__] = {}; | |
334 | #endif /* UNCORE_PER_CLUSTER */ | |
335 | ||
336 | #if UNCORE_VERSION >= 2 | |
337 | /* | |
338 | * V2 uncore monitors feature a CTI mechanism -- the second bit of UPMSR is | |
339 | * used to track if a CTI has been triggered due to an overflow. | |
340 | */ | |
341 | #define UPMSR_OVF_POS 2 | |
342 | #else /* UNCORE_VERSION >= 2 */ | |
343 | #define UPMSR_OVF_POS 1 | |
344 | #endif /* UNCORE_VERSION < 2 */ | |
345 | #define UPMSR_OVF(R, CTR) ((R) >> ((CTR) + UPMSR_OVF_POS) & 0x1) | |
346 | #define UPMSR_OVF_MASK (((UINT64_C(1) << UNCORE_NCTRS) - 1) << UPMSR_OVF_POS) | |
347 | ||
348 | #define UPMPCM "s3_7_c15_c5_4" | |
349 | #define UPMPCM_CORE(ID) (UINT64_C(1) << (ID)) | |
350 | ||
351 | /* | |
352 | * The uncore_pmi_mask is a bitmask of CPUs that receive uncore PMIs. It's | |
353 | * initialized by uncore_init and controllable by the uncore_pmi_mask boot-arg. | |
354 | */ | |
355 | static int32_t uncore_pmi_mask = 0; | |
356 | ||
357 | /* | |
358 | * The uncore_active_ctrs is a bitmask of uncore counters that are currently | |
359 | * requested. | |
360 | */ | |
361 | static uint16_t uncore_active_ctrs = 0; | |
362 | static_assert(sizeof(uncore_active_ctrs) * CHAR_BIT >= UNCORE_NCTRS, | |
363 | "counter mask should fit the full range of counters"); | |
364 | ||
365 | /* | |
366 | * mt_uncore_enabled is true when any uncore counters are active. | |
367 | */ | |
368 | bool mt_uncore_enabled = false; | |
369 | ||
370 | /* | |
371 | * Each uncore unit has its own monitor, corresponding to the memory hierarchy | |
372 | * of the LLCs. | |
373 | */ | |
374 | #if UNCORE_PER_CLUSTER | |
375 | #define UNCORE_NMONITORS (__ARM_CLUSTER_COUNT__) | |
376 | #else /* UNCORE_PER_CLUSTER */ | |
377 | #define UNCORE_NMONITORS (1) | |
378 | #endif /* !UNCORE_PER_CLUSTER */ | |
379 | ||
380 | /* | |
381 | * The uncore_events are the event configurations for each uncore counter -- as | |
382 | * a union to make it easy to program the hardware registers. | |
383 | */ | |
384 | static struct uncore_config { | |
385 | union { | |
386 | uint8_t uce_ctrs[UNCORE_NCTRS]; | |
387 | uint64_t uce_regs[UNCORE_NCTRS / 8]; | |
388 | } uc_events; | |
389 | union { | |
390 | uint16_t uccm_masks[UNCORE_NCTRS]; | |
391 | uint64_t uccm_regs[UNCORE_NCTRS / 4]; | |
392 | } uc_cpu_masks[UNCORE_NMONITORS]; | |
393 | } uncore_config; | |
394 | ||
395 | static struct uncore_monitor { | |
396 | /* | |
397 | * The last snapshot of each of the hardware counter values. | |
398 | */ | |
399 | uint64_t um_snaps[UNCORE_NCTRS]; | |
400 | ||
401 | /* | |
402 | * The accumulated counts for each counter. | |
403 | */ | |
404 | uint64_t um_counts[UNCORE_NCTRS]; | |
405 | ||
406 | /* | |
407 | * Protects accessing the hardware registers and fields in this structure. | |
408 | */ | |
409 | lck_spin_t um_lock; | |
410 | ||
411 | /* | |
412 | * Whether this monitor needs its registers restored after wake. | |
413 | */ | |
414 | bool um_sleeping; | |
415 | } uncore_monitors[UNCORE_NMONITORS]; | |
416 | ||
417 | static unsigned int | |
418 | uncmon_get_curid(void) | |
419 | { | |
420 | #if UNCORE_PER_CLUSTER | |
421 | return cpu_cluster_id(); | |
422 | #else /* UNCORE_PER_CLUSTER */ | |
423 | return 0; | |
424 | #endif /* !UNCORE_PER_CLUSTER */ | |
425 | } | |
426 | ||
427 | /* | |
428 | * Per-monitor locks are required to prevent races with the PMI handlers, not | |
429 | * from other CPUs that are configuring (those are serialized with monotonic's | |
430 | * per-device lock). | |
431 | */ | |
432 | ||
433 | static int | |
434 | uncmon_lock(struct uncore_monitor *mon) | |
435 | { | |
436 | int intrs_en = ml_set_interrupts_enabled(FALSE); | |
437 | lck_spin_lock(&mon->um_lock); | |
438 | return intrs_en; | |
439 | } | |
440 | ||
441 | static void | |
442 | uncmon_unlock(struct uncore_monitor *mon, int intrs_en) | |
443 | { | |
444 | lck_spin_unlock(&mon->um_lock); | |
445 | (void)ml_set_interrupts_enabled(intrs_en); | |
446 | } | |
447 | ||
448 | /* | |
449 | * Helper functions for accessing the hardware -- these require the monitor be | |
450 | * locked to prevent other CPUs' PMI handlers from making local modifications | |
451 | * or updating the counts. | |
452 | */ | |
453 | ||
454 | #if UNCORE_VERSION >= 2 | |
455 | #define UPMCR0_INTEN_POS 20 | |
456 | #define UPMCR0_INTGEN_POS 16 | |
457 | #else /* UNCORE_VERSION >= 2 */ | |
458 | #define UPMCR0_INTEN_POS 12 | |
459 | #define UPMCR0_INTGEN_POS 8 | |
460 | #endif /* UNCORE_VERSION < 2 */ | |
461 | enum { | |
462 | UPMCR0_INTGEN_OFF = 0, | |
463 | /* fast PMIs are only supported on core CPMU */ | |
464 | UPMCR0_INTGEN_AIC = 2, | |
465 | UPMCR0_INTGEN_HALT = 3, | |
466 | UPMCR0_INTGEN_FIQ = 4, | |
467 | }; | |
468 | /* always enable interrupts for all counters */ | |
469 | #define UPMCR0_INTEN (((1ULL << UNCORE_NCTRS) - 1) << UPMCR0_INTEN_POS) | |
470 | /* route uncore PMIs through the FIQ path */ | |
471 | #define UPMCR0_INIT (UPMCR0_INTEN | (UPMCR0_INTGEN_FIQ << UPMCR0_INTGEN_POS)) | |
472 | ||
473 | /* | |
474 | * Turn counting on for counters set in the `enctrmask` and off, otherwise. | |
475 | */ | |
476 | static inline void | |
477 | uncmon_set_counting_locked_l(__unused unsigned int monid, uint64_t enctrmask) | |
478 | { | |
479 | /* | |
480 | * UPMCR0 controls which counters are enabled and how interrupts are generated | |
481 | * for overflows. | |
482 | */ | |
483 | #define UPMCR0 "s3_7_c15_c0_4" | |
484 | __builtin_arm_wsr64(UPMCR0, UPMCR0_INIT | enctrmask); | |
485 | } | |
486 | ||
487 | #if UNCORE_PER_CLUSTER | |
488 | ||
489 | /* | |
490 | * Turn counting on for counters set in the `enctrmask` and off, otherwise. | |
491 | */ | |
492 | static inline void | |
493 | uncmon_set_counting_locked_r(unsigned int monid, uint64_t enctrmask) | |
494 | { | |
495 | const uintptr_t upmcr0_offset = 0x4180; | |
496 | *(uint64_t *)(cpm_impl[monid] + upmcr0_offset) = UPMCR0_INIT | enctrmask; | |
497 | } | |
498 | ||
499 | #endif /* UNCORE_PER_CLUSTER */ | |
500 | ||
501 | /* | |
502 | * The uncore performance monitoring counters (UPMCs) are 48-bits wide. The | |
503 | * high bit is an overflow bit, triggering a PMI, providing 47 usable bits. | |
504 | */ | |
505 | ||
506 | #define UPMC_MAX ((UINT64_C(1) << 48) - 1) | |
507 | ||
508 | /* | |
509 | * The `__builtin_arm_{r,w}sr` functions require constant strings, since the | |
510 | * MSR/MRS instructions encode the registers as immediates. Otherwise, this | |
511 | * would be indexing into an array of strings. | |
512 | */ | |
513 | ||
514 | #define UPMC0 "s3_7_c15_c7_4" | |
515 | #define UPMC1 "s3_7_c15_c8_4" | |
516 | #define UPMC2 "s3_7_c15_c9_4" | |
517 | #define UPMC3 "s3_7_c15_c10_4" | |
518 | #define UPMC4 "s3_7_c15_c11_4" | |
519 | #define UPMC5 "s3_7_c15_c12_4" | |
520 | #define UPMC6 "s3_7_c15_c13_4" | |
521 | #define UPMC7 "s3_7_c15_c14_4" | |
522 | #if UNCORE_NCTRS > 8 | |
523 | #define UPMC8 "s3_7_c15_c0_5" | |
524 | #define UPMC9 "s3_7_c15_c1_5" | |
525 | #define UPMC10 "s3_7_c15_c2_5" | |
526 | #define UPMC11 "s3_7_c15_c3_5" | |
527 | #define UPMC12 "s3_7_c15_c4_5" | |
528 | #define UPMC13 "s3_7_c15_c5_5" | |
529 | #define UPMC14 "s3_7_c15_c6_5" | |
530 | #define UPMC15 "s3_7_c15_c7_5" | |
531 | #endif /* UNCORE_NCTRS > 8 */ | |
532 | ||
533 | #define UPMC_0_7(X, A) X(0, A); X(1, A); X(2, A); X(3, A); X(4, A); X(5, A); \ | |
534 | X(6, A); X(7, A) | |
535 | #if UNCORE_NCTRS <= 8 | |
536 | #define UPMC_ALL(X, A) UPMC_0_7(X, A) | |
537 | #else /* UNCORE_NCTRS <= 8 */ | |
538 | #define UPMC_8_15(X, A) X(8, A); X(9, A); X(10, A); X(11, A); X(12, A); \ | |
539 | X(13, A); X(14, A); X(15, A) | |
540 | #define UPMC_ALL(X, A) UPMC_0_7(X, A); UPMC_8_15(X, A) | |
541 | #endif /* UNCORE_NCTRS > 8 */ | |
542 | ||
543 | static inline uint64_t | |
544 | uncmon_read_counter_locked_l(__unused unsigned int monid, unsigned int ctr) | |
545 | { | |
546 | assert(ctr < UNCORE_NCTRS); | |
547 | switch (ctr) { | |
548 | #define UPMC_RD(CTR, UNUSED) case (CTR): return __builtin_arm_rsr64(UPMC ## CTR) | |
549 | UPMC_ALL(UPMC_RD, 0); | |
550 | #undef UPMC_RD | |
551 | default: | |
552 | panic("monotonic: invalid counter read %u", ctr); | |
553 | __builtin_unreachable(); | |
554 | } | |
555 | } | |
556 | ||
557 | static inline void | |
558 | uncmon_write_counter_locked_l(__unused unsigned int monid, unsigned int ctr, | |
559 | uint64_t count) | |
560 | { | |
561 | assert(count < UPMC_MAX); | |
562 | assert(ctr < UNCORE_NCTRS); | |
563 | switch (ctr) { | |
564 | #define UPMC_WR(CTR, COUNT) case (CTR): \ | |
565 | return __builtin_arm_wsr64(UPMC ## CTR, (COUNT)) | |
566 | UPMC_ALL(UPMC_WR, count); | |
567 | #undef UPMC_WR | |
568 | default: | |
569 | panic("monotonic: invalid counter write %u", ctr); | |
570 | } | |
571 | } | |
572 | ||
573 | #if UNCORE_PER_CLUSTER | |
574 | ||
575 | static const uint8_t clust_offs[__ARM_CLUSTER_COUNT__] = CPU_CLUSTER_OFFSETS; | |
576 | ||
577 | uintptr_t upmc_offs[UNCORE_NCTRS] = { | |
578 | [0] = 0x4100, [1] = 0x4248, [2] = 0x4110, [3] = 0x4250, [4] = 0x4120, | |
579 | [5] = 0x4258, [6] = 0x4130, [7] = 0x4260, [8] = 0x4140, [9] = 0x4268, | |
580 | [10] = 0x4150, [11] = 0x4270, [12] = 0x4160, [13] = 0x4278, | |
581 | [14] = 0x4170, [15] = 0x4280, | |
582 | }; | |
583 | ||
584 | static inline uint64_t | |
585 | uncmon_read_counter_locked_r(unsigned int mon_id, unsigned int ctr) | |
586 | { | |
587 | assert(mon_id < __ARM_CLUSTER_COUNT__); | |
588 | assert(ctr < UNCORE_NCTRS); | |
589 | return *(uint64_t *)(cpm_impl[mon_id] + upmc_offs[ctr]); | |
590 | } | |
591 | ||
592 | static inline void | |
593 | uncmon_write_counter_locked_r(unsigned int mon_id, unsigned int ctr, | |
594 | uint64_t count) | |
595 | { | |
596 | assert(count < UPMC_MAX); | |
597 | assert(ctr < UNCORE_NCTRS); | |
598 | assert(mon_id < __ARM_CLUSTER_COUNT__); | |
599 | *(uint64_t *)(cpm_impl[mon_id] + upmc_offs[ctr]) = count; | |
600 | } | |
601 | ||
602 | #endif /* UNCORE_PER_CLUSTER */ | |
603 | ||
604 | static inline void | |
605 | uncmon_update_locked(unsigned int monid, unsigned int curid, unsigned int ctr) | |
606 | { | |
607 | struct uncore_monitor *mon = &uncore_monitors[monid]; | |
608 | uint64_t snap = 0; | |
609 | if (curid == monid) { | |
610 | snap = uncmon_read_counter_locked_l(monid, ctr); | |
611 | } else { | |
612 | #if UNCORE_PER_CLUSTER | |
613 | snap = uncmon_read_counter_locked_r(monid, ctr); | |
614 | #endif /* UNCORE_PER_CLUSTER */ | |
615 | } | |
616 | /* counters should increase monotonically */ | |
617 | assert(snap >= mon->um_snaps[ctr]); | |
618 | mon->um_counts[ctr] += snap - mon->um_snaps[ctr]; | |
619 | mon->um_snaps[ctr] = snap; | |
620 | } | |
621 | ||
622 | static inline void | |
623 | uncmon_program_events_locked_l(unsigned int monid) | |
624 | { | |
625 | /* | |
626 | * UPMESR[01] is the event selection register that determines which event a | |
627 | * counter will count. | |
628 | */ | |
629 | #define UPMESR0 "s3_7_c15_c1_4" | |
630 | CTRL_REG_SET(UPMESR0, uncore_config.uc_events.uce_regs[0]); | |
631 | ||
632 | #if UNCORE_NCTRS > 8 | |
633 | #define UPMESR1 "s3_7_c15_c11_5" | |
634 | CTRL_REG_SET(UPMESR1, uncore_config.uc_events.uce_regs[1]); | |
635 | #endif /* UNCORE_NCTRS > 8 */ | |
636 | ||
637 | /* | |
638 | * UPMECM[0123] are the event core masks for each counter -- whether or not | |
639 | * that counter counts events generated by an agent. These are set to all | |
640 | * ones so the uncore counters count events from all cores. | |
641 | * | |
642 | * The bits are based off the start of the cluster -- e.g. even if a core | |
643 | * has a CPU ID of 4, it might be the first CPU in a cluster. Shift the | |
644 | * registers right by the ID of the first CPU in the cluster. | |
645 | */ | |
646 | #define UPMECM0 "s3_7_c15_c3_4" | |
647 | #define UPMECM1 "s3_7_c15_c4_4" | |
648 | ||
649 | CTRL_REG_SET(UPMECM0, | |
650 | uncore_config.uc_cpu_masks[monid].uccm_regs[0]); | |
651 | CTRL_REG_SET(UPMECM1, | |
652 | uncore_config.uc_cpu_masks[monid].uccm_regs[1]); | |
653 | ||
654 | #if UNCORE_NCTRS > 8 | |
655 | #define UPMECM2 "s3_7_c15_c8_5" | |
656 | #define UPMECM3 "s3_7_c15_c9_5" | |
657 | ||
658 | CTRL_REG_SET(UPMECM2, | |
659 | uncore_config.uc_cpu_masks[monid].uccm_regs[2]); | |
660 | CTRL_REG_SET(UPMECM3, | |
661 | uncore_config.uc_cpu_masks[monid].uccm_regs[3]); | |
662 | #endif /* UNCORE_NCTRS > 8 */ | |
663 | } | |
664 | ||
665 | #if UNCORE_PER_CLUSTER | |
666 | ||
667 | static inline void | |
668 | uncmon_program_events_locked_r(unsigned int monid) | |
669 | { | |
670 | const uintptr_t upmesr_offs[2] = {[0] = 0x41b0, [1] = 0x41b8, }; | |
671 | ||
672 | for (unsigned int i = 0; i < sizeof(upmesr_offs) / sizeof(upmesr_offs[0]); | |
673 | i++) { | |
674 | *(uint64_t *)(cpm_impl[monid] + upmesr_offs[i]) = | |
675 | uncore_config.uc_events.uce_regs[i]; | |
676 | } | |
677 | ||
678 | const uintptr_t upmecm_offs[4] = { | |
679 | [0] = 0x4190, [1] = 0x4198, [2] = 0x41a0, [3] = 0x41a8, | |
680 | }; | |
681 | ||
682 | for (unsigned int i = 0; i < sizeof(upmecm_offs) / sizeof(upmecm_offs[0]); | |
683 | i++) { | |
684 | *(uint64_t *)(cpm_impl[monid] + upmecm_offs[i]) = | |
685 | uncore_config.uc_cpu_masks[monid].uccm_regs[i]; | |
686 | } | |
687 | } | |
688 | ||
689 | #endif /* UNCORE_PER_CLUSTER */ | |
690 | ||
691 | static void | |
692 | uncmon_clear_int_locked_l(__unused unsigned int monid) | |
693 | { | |
694 | __builtin_arm_wsr64(UPMSR, 0); | |
695 | } | |
696 | ||
697 | #if UNCORE_PER_CLUSTER | |
698 | ||
699 | static void | |
700 | uncmon_clear_int_locked_r(unsigned int monid) | |
701 | { | |
702 | const uintptr_t upmsr_off = 0x41c0; | |
703 | *(uint64_t *)(cpm_impl[monid] + upmsr_off) = 0; | |
704 | } | |
705 | ||
706 | #endif /* UNCORE_PER_CLUSTER */ | |
707 | ||
708 | /* | |
709 | * Get the PMI mask for the provided `monid` -- that is, the bitmap of CPUs | |
710 | * that should be sent PMIs for a particular monitor. | |
711 | */ | |
712 | static uint64_t | |
713 | uncmon_get_pmi_mask(unsigned int monid) | |
714 | { | |
715 | uint64_t pmi_mask = uncore_pmi_mask; | |
716 | ||
717 | #if UNCORE_PER_CLUSTER | |
718 | /* | |
719 | * Set up the mask for the high bits. | |
720 | */ | |
721 | uint64_t clust_cpumask; | |
722 | if (monid == __ARM_CLUSTER_COUNT__ - 1) { | |
723 | clust_cpumask = UINT64_MAX; | |
724 | } else { | |
725 | clust_cpumask = ((1ULL << clust_offs[monid + 1]) - 1); | |
726 | } | |
727 | ||
728 | /* | |
729 | * Mask off the low bits, if necessary. | |
730 | */ | |
731 | if (clust_offs[monid] != 0) { | |
732 | clust_cpumask &= ~((1ULL << clust_offs[monid]) - 1); | |
733 | } | |
734 | ||
735 | pmi_mask &= clust_cpumask; | |
736 | #else /* UNCORE_PER_CLUSTER */ | |
737 | #pragma unused(monid) | |
738 | #endif /* !UNCORE_PER_CLUSTER */ | |
739 | ||
740 | return pmi_mask; | |
741 | } | |
742 | ||
743 | /* | |
744 | * Initialization routines for the uncore counters. | |
745 | */ | |
746 | ||
747 | static void | |
748 | uncmon_init_locked_l(unsigned int monid) | |
749 | { | |
750 | /* | |
751 | * UPMPCM defines the PMI core mask for the UPMCs -- which cores should | |
752 | * receive interrupts on overflow. | |
753 | */ | |
754 | CTRL_REG_SET(UPMPCM, uncmon_get_pmi_mask(monid)); | |
755 | uncmon_set_counting_locked_l(monid, | |
756 | mt_uncore_enabled ? uncore_active_ctrs : 0); | |
757 | } | |
758 | ||
759 | #if UNCORE_PER_CLUSTER | |
760 | ||
761 | static vm_size_t acc_impl_size = 0; | |
762 | static uintptr_t acc_impl[__ARM_CLUSTER_COUNT__] = {}; | |
763 | static uintptr_t acc_impl_phys[__ARM_CLUSTER_COUNT__] = {}; | |
764 | ||
765 | static void | |
766 | uncmon_init_locked_r(unsigned int monid) | |
767 | { | |
768 | const uintptr_t upmpcm_off = 0x1010; | |
769 | ||
770 | *(uint64_t *)(acc_impl[monid] + upmpcm_off) = uncmon_get_pmi_mask(monid); | |
771 | uncmon_set_counting_locked_r(monid, | |
772 | mt_uncore_enabled ? uncore_active_ctrs : 0); | |
773 | } | |
774 | ||
775 | #endif /* UNCORE_PER_CLUSTER */ | |
776 | ||
777 | /* | |
778 | * Initialize the uncore device for monotonic. | |
779 | */ | |
780 | static int | |
781 | uncore_init(__unused mt_device_t dev) | |
782 | { | |
783 | #if DEVELOPMENT || DEBUG | |
784 | /* | |
785 | * Development and debug kernels observe the `uncore_pmi_mask` boot-arg, | |
786 | * allowing PMIs to be routed to the CPUs present in the supplied bitmap. | |
787 | * Do some sanity checks on the value provided. | |
788 | */ | |
789 | bool parsed_arg = PE_parse_boot_argn("uncore_pmi_mask", &uncore_pmi_mask, | |
790 | sizeof(uncore_pmi_mask)); | |
791 | if (parsed_arg) { | |
792 | #if UNCORE_PER_CLUSTER | |
793 | if (__builtin_popcount(uncore_pmi_mask) != __ARM_CLUSTER_COUNT__) { | |
794 | panic("monotonic: invalid uncore PMI mask 0x%x", uncore_pmi_mask); | |
795 | } | |
796 | for (unsigned int i = 0; i < __ARM_CLUSTER_COUNT__; i++) { | |
797 | if (__builtin_popcountll(uncmon_get_pmi_mask(i)) != 1) { | |
798 | panic("monotonic: invalid uncore PMI CPU for cluster %d in mask 0x%x", | |
799 | i, uncore_pmi_mask); | |
800 | } | |
801 | } | |
802 | #else /* UNCORE_PER_CLUSTER */ | |
803 | if (__builtin_popcount(uncore_pmi_mask) != 1) { | |
804 | panic("monotonic: invalid uncore PMI mask 0x%x", uncore_pmi_mask); | |
805 | } | |
806 | #endif /* !UNCORE_PER_CLUSTER */ | |
807 | } else | |
808 | #endif /* DEVELOPMENT || DEBUG */ | |
809 | { | |
810 | #if UNCORE_PER_CLUSTER | |
811 | for (int i = 0; i < __ARM_CLUSTER_COUNT__; i++) { | |
812 | /* route to the first CPU in each cluster */ | |
813 | uncore_pmi_mask |= (1ULL << clust_offs[i]); | |
814 | } | |
815 | #else /* UNCORE_PER_CLUSTER */ | |
816 | /* arbitrarily route to core 0 */ | |
817 | uncore_pmi_mask |= 1; | |
818 | #endif /* !UNCORE_PER_CLUSTER */ | |
819 | } | |
820 | assert(uncore_pmi_mask != 0); | |
821 | ||
822 | unsigned int curmonid = uncmon_get_curid(); | |
823 | ||
824 | for (unsigned int monid = 0; monid < UNCORE_NMONITORS; monid++) { | |
825 | #if UNCORE_PER_CLUSTER | |
826 | cpm_impl[monid] = (uintptr_t)ml_io_map(cpm_impl_phys[monid], | |
827 | cpm_impl_size); | |
828 | assert(cpm_impl[monid] != 0); | |
829 | ||
830 | acc_impl[monid] = (uintptr_t)ml_io_map(acc_impl_phys[monid], | |
831 | acc_impl_size); | |
832 | assert(acc_impl[monid] != 0); | |
833 | #endif /* UNCORE_PER_CLUSTER */ | |
834 | ||
835 | struct uncore_monitor *mon = &uncore_monitors[monid]; | |
836 | lck_spin_init(&mon->um_lock, mt_lock_grp, NULL); | |
837 | ||
838 | int intrs_en = uncmon_lock(mon); | |
839 | if (monid != curmonid) { | |
840 | #if UNCORE_PER_CLUSTER | |
841 | uncmon_init_locked_r(monid); | |
842 | #endif /* UNCORE_PER_CLUSTER */ | |
843 | } else { | |
844 | uncmon_init_locked_l(monid); | |
845 | } | |
846 | uncmon_unlock(mon, intrs_en); | |
847 | } | |
848 | ||
849 | mt_uncore_initted = true; | |
850 | ||
851 | return 0; | |
852 | } | |
853 | ||
854 | /* | |
855 | * Support for monotonic's mtd_read function. | |
856 | */ | |
857 | ||
858 | static void | |
859 | uncmon_read_all_counters(unsigned int monid, unsigned int curmonid, | |
860 | uint64_t ctr_mask, uint64_t *counts) | |
861 | { | |
862 | struct uncore_monitor *mon = &uncore_monitors[monid]; | |
863 | ||
864 | int intrs_en = uncmon_lock(mon); | |
865 | ||
866 | for (unsigned int ctr = 0; ctr < UNCORE_NCTRS; ctr++) { | |
867 | if (ctr_mask & (1ULL << ctr)) { | |
868 | uncmon_update_locked(monid, curmonid, ctr); | |
869 | counts[ctr] = mon->um_counts[ctr]; | |
870 | } | |
871 | } | |
872 | ||
873 | uncmon_unlock(mon, intrs_en); | |
874 | } | |
875 | ||
876 | /* | |
877 | * Read all monitor's counters. | |
878 | */ | |
879 | static int | |
880 | uncore_read(uint64_t ctr_mask, uint64_t *counts_out) | |
881 | { | |
882 | assert(ctr_mask != 0); | |
883 | assert(counts_out != NULL); | |
884 | ||
885 | if (!uncore_active_ctrs) { | |
886 | return EPWROFF; | |
887 | } | |
888 | if (ctr_mask & ~uncore_active_ctrs) { | |
889 | return EINVAL; | |
890 | } | |
891 | ||
892 | unsigned int curmonid = uncmon_get_curid(); | |
893 | for (unsigned int monid = 0; monid < UNCORE_NMONITORS; monid++) { | |
894 | /* | |
895 | * Find this monitor's starting offset into the `counts_out` array. | |
896 | */ | |
897 | uint64_t *counts = counts_out + (UNCORE_NCTRS * monid); | |
898 | ||
899 | uncmon_read_all_counters(monid, curmonid, ctr_mask, counts); | |
900 | } | |
901 | ||
902 | return 0; | |
903 | } | |
904 | ||
905 | /* | |
906 | * Support for monotonic's mtd_add function. | |
907 | */ | |
908 | ||
909 | /* | |
910 | * Add an event to the current uncore configuration. This doesn't take effect | |
911 | * until the counters are enabled again, so there's no need to involve the | |
912 | * monitors. | |
913 | */ | |
914 | static int | |
915 | uncore_add(struct monotonic_config *config, uint32_t *ctr_out) | |
916 | { | |
917 | if (mt_uncore_enabled) { | |
918 | return EBUSY; | |
919 | } | |
920 | ||
921 | uint32_t available = ~uncore_active_ctrs & config->allowed_ctr_mask; | |
922 | ||
923 | if (available == 0) { | |
924 | return ENOSPC; | |
925 | } | |
926 | ||
927 | uint32_t valid_ctrs = (UINT32_C(1) << UNCORE_NCTRS) - 1; | |
928 | if ((available & valid_ctrs) == 0) { | |
929 | return E2BIG; | |
930 | } | |
931 | ||
932 | uint32_t ctr = __builtin_ffsll(available) - 1; | |
933 | ||
934 | uncore_active_ctrs |= UINT64_C(1) << ctr; | |
935 | uncore_config.uc_events.uce_ctrs[ctr] = config->event; | |
936 | uint64_t cpu_mask = UINT64_MAX; | |
937 | if (config->cpu_mask != 0) { | |
938 | cpu_mask = config->cpu_mask; | |
939 | } | |
940 | for (int i = 0; i < UNCORE_NMONITORS; i++) { | |
941 | #if UNCORE_PER_CLUSTER | |
942 | const unsigned int shift = clust_offs[i]; | |
943 | #else /* UNCORE_PER_CLUSTER */ | |
944 | const unsigned int shift = 0; | |
945 | #endif /* !UNCORE_PER_CLUSTER */ | |
946 | uncore_config.uc_cpu_masks[i].uccm_masks[ctr] = cpu_mask >> shift; | |
947 | } | |
948 | ||
949 | *ctr_out = ctr; | |
950 | return 0; | |
951 | } | |
952 | ||
953 | /* | |
954 | * Support for monotonic's mtd_reset function. | |
955 | */ | |
956 | ||
957 | /* | |
958 | * Reset all configuration and disable the counters if they're currently | |
959 | * counting. | |
960 | */ | |
961 | static void | |
962 | uncore_reset(void) | |
963 | { | |
964 | mt_uncore_enabled = false; | |
965 | ||
966 | unsigned int curmonid = uncmon_get_curid(); | |
967 | ||
968 | for (unsigned int monid = 0; monid < UNCORE_NMONITORS; monid++) { | |
969 | struct uncore_monitor *mon = &uncore_monitors[monid]; | |
970 | bool remote = monid != curmonid; | |
971 | ||
972 | int intrs_en = uncmon_lock(mon); | |
973 | if (remote) { | |
974 | #if UNCORE_PER_CLUSTER | |
975 | uncmon_set_counting_locked_r(monid, 0); | |
976 | #endif /* UNCORE_PER_CLUSTER */ | |
977 | } else { | |
978 | uncmon_set_counting_locked_l(monid, 0); | |
979 | } | |
980 | ||
981 | for (int ctr = 0; ctr < UNCORE_NCTRS; ctr++) { | |
982 | if (uncore_active_ctrs & (1U << ctr)) { | |
983 | if (remote) { | |
984 | #if UNCORE_PER_CLUSTER | |
985 | uncmon_write_counter_locked_r(monid, ctr, 0); | |
986 | #endif /* UNCORE_PER_CLUSTER */ | |
987 | } else { | |
988 | uncmon_write_counter_locked_l(monid, ctr, 0); | |
989 | } | |
990 | } | |
991 | } | |
992 | ||
993 | memset(&mon->um_snaps, 0, sizeof(mon->um_snaps)); | |
994 | memset(&mon->um_counts, 0, sizeof(mon->um_counts)); | |
995 | if (remote) { | |
996 | #if UNCORE_PER_CLUSTER | |
997 | uncmon_clear_int_locked_r(monid); | |
998 | #endif /* UNCORE_PER_CLUSTER */ | |
999 | } else { | |
1000 | uncmon_clear_int_locked_l(monid); | |
1001 | } | |
1002 | ||
1003 | uncmon_unlock(mon, intrs_en); | |
1004 | } | |
1005 | ||
1006 | uncore_active_ctrs = 0; | |
1007 | memset(&uncore_config, 0, sizeof(uncore_config)); | |
1008 | ||
1009 | for (unsigned int monid = 0; monid < UNCORE_NMONITORS; monid++) { | |
1010 | struct uncore_monitor *mon = &uncore_monitors[monid]; | |
1011 | bool remote = monid != curmonid; | |
1012 | ||
1013 | int intrs_en = uncmon_lock(mon); | |
1014 | if (remote) { | |
1015 | #if UNCORE_PER_CLUSTER | |
1016 | uncmon_program_events_locked_r(monid); | |
1017 | #endif /* UNCORE_PER_CLUSTER */ | |
1018 | } else { | |
1019 | uncmon_program_events_locked_l(monid); | |
1020 | } | |
1021 | uncmon_unlock(mon, intrs_en); | |
1022 | } | |
1023 | } | |
1024 | ||
1025 | /* | |
1026 | * Support for monotonic's mtd_enable function. | |
1027 | */ | |
1028 | ||
1029 | static void | |
1030 | uncmon_set_enabled_l(unsigned int monid, bool enable) | |
1031 | { | |
1032 | struct uncore_monitor *mon = &uncore_monitors[monid]; | |
1033 | int intrs_en = uncmon_lock(mon); | |
1034 | ||
1035 | if (enable) { | |
1036 | uncmon_program_events_locked_l(monid); | |
1037 | uncmon_set_counting_locked_l(monid, uncore_active_ctrs); | |
1038 | } else { | |
1039 | uncmon_set_counting_locked_l(monid, 0); | |
1040 | } | |
1041 | ||
1042 | uncmon_unlock(mon, intrs_en); | |
1043 | } | |
1044 | ||
1045 | #if UNCORE_PER_CLUSTER | |
1046 | ||
1047 | static void | |
1048 | uncmon_set_enabled_r(unsigned int monid, bool enable) | |
1049 | { | |
1050 | struct uncore_monitor *mon = &uncore_monitors[monid]; | |
1051 | int intrs_en = uncmon_lock(mon); | |
1052 | ||
1053 | if (enable) { | |
1054 | uncmon_program_events_locked_r(monid); | |
1055 | uncmon_set_counting_locked_r(monid, uncore_active_ctrs); | |
1056 | } else { | |
1057 | uncmon_set_counting_locked_r(monid, 0); | |
1058 | } | |
1059 | ||
1060 | uncmon_unlock(mon, intrs_en); | |
1061 | } | |
1062 | ||
1063 | #endif /* UNCORE_PER_CLUSTER */ | |
1064 | ||
1065 | static void | |
1066 | uncore_set_enabled(bool enable) | |
1067 | { | |
1068 | mt_uncore_enabled = enable; | |
1069 | ||
1070 | unsigned int curmonid = uncmon_get_curid(); | |
1071 | for (unsigned int monid = 0; monid < UNCORE_NMONITORS; monid++) { | |
1072 | if (monid != curmonid) { | |
1073 | #if UNCORE_PER_CLUSTER | |
1074 | uncmon_set_enabled_r(monid, enable); | |
1075 | #endif /* UNCORE_PER_CLUSTER */ | |
1076 | } else { | |
1077 | uncmon_set_enabled_l(monid, enable); | |
1078 | } | |
1079 | } | |
1080 | } | |
1081 | ||
1082 | /* | |
1083 | * Hooks in the machine layer. | |
1084 | */ | |
1085 | ||
1086 | static void | |
1087 | uncore_fiq(uint64_t upmsr) | |
1088 | { | |
1089 | /* | |
1090 | * Determine which counters overflowed. | |
1091 | */ | |
1092 | uint64_t disable_ctr_mask = (upmsr & UPMSR_OVF_MASK) >> UPMSR_OVF_POS; | |
1093 | /* should not receive interrupts from inactive counters */ | |
1094 | assert(!(disable_ctr_mask & ~uncore_active_ctrs)); | |
1095 | ||
1096 | unsigned int monid = uncmon_get_curid(); | |
1097 | struct uncore_monitor *mon = &uncore_monitors[monid]; | |
1098 | ||
1099 | int intrs_en = uncmon_lock(mon); | |
1100 | ||
1101 | /* | |
1102 | * Disable any counters that overflowed. | |
1103 | */ | |
1104 | uncmon_set_counting_locked_l(monid, | |
1105 | uncore_active_ctrs & ~disable_ctr_mask); | |
1106 | ||
1107 | /* | |
1108 | * With the overflowing counters disabled, capture their counts and reset | |
1109 | * the UPMCs and their snapshots to 0. | |
1110 | */ | |
1111 | for (unsigned int ctr = 0; ctr < UNCORE_NCTRS; ctr++) { | |
1112 | if (UPMSR_OVF(upmsr, ctr)) { | |
1113 | uncmon_update_locked(monid, monid, ctr); | |
1114 | mon->um_snaps[ctr] = 0; | |
1115 | uncmon_write_counter_locked_l(monid, ctr, 0); | |
1116 | } | |
1117 | } | |
1118 | ||
1119 | /* | |
1120 | * Acknowledge the interrupt, now that any overflowed PMCs have been reset. | |
1121 | */ | |
1122 | uncmon_clear_int_locked_l(monid); | |
1123 | ||
1124 | /* | |
1125 | * Re-enable all active counters. | |
1126 | */ | |
1127 | uncmon_set_counting_locked_l(monid, uncore_active_ctrs); | |
1128 | ||
1129 | uncmon_unlock(mon, intrs_en); | |
1130 | } | |
1131 | ||
1132 | static void | |
1133 | uncore_save(void) | |
1134 | { | |
1135 | if (!uncore_active_ctrs) { | |
1136 | return; | |
1137 | } | |
1138 | ||
1139 | unsigned int curmonid = uncmon_get_curid(); | |
1140 | ||
1141 | for (unsigned int monid = 0; monid < UNCORE_NMONITORS; monid++) { | |
1142 | struct uncore_monitor *mon = &uncore_monitors[monid]; | |
1143 | int intrs_en = uncmon_lock(mon); | |
1144 | ||
1145 | if (mt_uncore_enabled) { | |
1146 | if (monid != curmonid) { | |
1147 | #if UNCORE_PER_CLUSTER | |
1148 | uncmon_set_counting_locked_r(monid, 0); | |
1149 | #endif /* UNCORE_PER_CLUSTER */ | |
1150 | } else { | |
1151 | uncmon_set_counting_locked_l(monid, 0); | |
1152 | } | |
1153 | } | |
1154 | ||
1155 | for (unsigned int ctr = 0; ctr < UNCORE_NCTRS; ctr++) { | |
1156 | if (uncore_active_ctrs & (1U << ctr)) { | |
1157 | uncmon_update_locked(monid, curmonid, ctr); | |
1158 | } | |
1159 | } | |
1160 | ||
1161 | mon->um_sleeping = true; | |
1162 | uncmon_unlock(mon, intrs_en); | |
1163 | } | |
1164 | } | |
1165 | ||
1166 | static void | |
1167 | uncore_restore(void) | |
1168 | { | |
1169 | if (!uncore_active_ctrs) { | |
1170 | return; | |
1171 | } | |
1172 | unsigned int curmonid = uncmon_get_curid(); | |
1173 | ||
1174 | struct uncore_monitor *mon = &uncore_monitors[curmonid]; | |
1175 | int intrs_en = uncmon_lock(mon); | |
1176 | if (!mon->um_sleeping) { | |
1177 | goto out; | |
1178 | } | |
1179 | ||
1180 | for (unsigned int ctr = 0; ctr < UNCORE_NCTRS; ctr++) { | |
1181 | if (uncore_active_ctrs & (1U << ctr)) { | |
1182 | uncmon_write_counter_locked_l(curmonid, ctr, mon->um_snaps[ctr]); | |
1183 | } | |
1184 | } | |
1185 | uncmon_program_events_locked_l(curmonid); | |
1186 | uncmon_init_locked_l(curmonid); | |
1187 | mon->um_sleeping = false; | |
1188 | ||
1189 | out: | |
1190 | uncmon_unlock(mon, intrs_en); | |
1191 | } | |
1192 | ||
1193 | static void | |
1194 | uncore_early_init(void) | |
1195 | { | |
1196 | #if UNCORE_PER_CLUSTER | |
1197 | /* | |
1198 | * Initialize the necessary PIO physical regions from the device tree. | |
1199 | */ | |
1200 | DTEntry armio_entry = NULL; | |
1201 | if ((DTFindEntry("name", "arm-io", &armio_entry) != kSuccess)) { | |
1202 | panic("unable to find arm-io DT entry"); | |
1203 | } | |
1204 | ||
1205 | uint64_t *regs; | |
1206 | unsigned int regs_size = 0; | |
1207 | if (DTGetProperty(armio_entry, "acc-impl", (void **)®s, ®s_size) != | |
1208 | kSuccess) { | |
1209 | panic("unable to find acc-impl DT property"); | |
1210 | } | |
1211 | /* | |
1212 | * Two 8-byte values are expected for each cluster -- the physical address | |
1213 | * of the region and its size. | |
1214 | */ | |
1215 | const unsigned int expected_size = | |
1216 | (typeof(expected_size))sizeof(uint64_t) * __ARM_CLUSTER_COUNT__ * 2; | |
1217 | if (regs_size != expected_size) { | |
1218 | panic("invalid size for acc-impl DT property"); | |
1219 | } | |
1220 | for (int i = 0; i < __ARM_CLUSTER_COUNT__; i++) { | |
1221 | acc_impl_phys[i] = regs[i * 2]; | |
1222 | } | |
1223 | acc_impl_size = regs[1]; | |
1224 | ||
1225 | regs_size = 0; | |
1226 | if (DTGetProperty(armio_entry, "cpm-impl", (void **)®s, ®s_size) != | |
1227 | kSuccess) { | |
1228 | panic("unable to find cpm-impl property"); | |
1229 | } | |
1230 | if (regs_size != expected_size) { | |
1231 | panic("invalid size for cpm-impl DT property"); | |
1232 | } | |
1233 | for (int i = 0; i < __ARM_CLUSTER_COUNT__; i++) { | |
1234 | cpm_impl_phys[i] = regs[i * 2]; | |
1235 | } | |
1236 | cpm_impl_size = regs[1]; | |
1237 | #endif /* UNCORE_PER_CLUSTER */ | |
1238 | } | |
1239 | ||
1240 | #endif /* HAS_UNCORE_CTRS */ | |
d9a64523 A |
1241 | |
1242 | #pragma mark common hooks | |
1243 | ||
cb323159 A |
1244 | void |
1245 | mt_early_init(void) | |
1246 | { | |
c6bf4f31 A |
1247 | #if HAS_UNCORE_CTRS |
1248 | uncore_early_init(); | |
1249 | #endif /* HAS_UNCORE_CTRS */ | |
cb323159 A |
1250 | } |
1251 | ||
d9a64523 A |
1252 | void |
1253 | mt_cpu_idle(cpu_data_t *cpu) | |
1254 | { | |
1255 | core_idle(cpu); | |
1256 | } | |
1257 | ||
1258 | void | |
1259 | mt_cpu_run(cpu_data_t *cpu) | |
5ba3f43e | 1260 | { |
5ba3f43e A |
1261 | struct mt_cpu *mtc; |
1262 | ||
1263 | assert(cpu != NULL); | |
1264 | assert(ml_get_interrupts_enabled() == FALSE); | |
1265 | ||
1266 | mtc = &cpu->cpu_monotonic; | |
1267 | ||
1268 | for (int i = 0; i < MT_CORE_NFIXED; i++) { | |
1269 | mt_core_set_snap(i, mtc->mtc_snaps[i]); | |
1270 | } | |
1271 | ||
1272 | /* re-enable the counters */ | |
1273 | core_init_execution_modes(); | |
1274 | ||
0a7de745 | 1275 | core_set_enabled(); |
5ba3f43e A |
1276 | } |
1277 | ||
5ba3f43e A |
1278 | void |
1279 | mt_cpu_down(cpu_data_t *cpu) | |
1280 | { | |
1281 | mt_cpu_idle(cpu); | |
1282 | } | |
1283 | ||
1284 | void | |
1285 | mt_cpu_up(cpu_data_t *cpu) | |
1286 | { | |
5ba3f43e A |
1287 | mt_cpu_run(cpu); |
1288 | } | |
1289 | ||
1290 | void | |
1291 | mt_sleep(void) | |
1292 | { | |
c6bf4f31 A |
1293 | #if HAS_UNCORE_CTRS |
1294 | uncore_save(); | |
1295 | #endif /* HAS_UNCORE_CTRS */ | |
5ba3f43e A |
1296 | } |
1297 | ||
1298 | void | |
d9a64523 | 1299 | mt_wake_per_core(void) |
5ba3f43e | 1300 | { |
c6bf4f31 A |
1301 | #if HAS_UNCORE_CTRS |
1302 | if (mt_uncore_initted) { | |
1303 | uncore_restore(); | |
1304 | } | |
1305 | #endif /* HAS_UNCORE_CTRS */ | |
5ba3f43e A |
1306 | } |
1307 | ||
cb323159 A |
1308 | uint64_t |
1309 | mt_count_pmis(void) | |
1310 | { | |
1311 | uint64_t npmis = 0; | |
1312 | int max_cpu = ml_get_max_cpu_number(); | |
1313 | for (int i = 0; i <= max_cpu; i++) { | |
1314 | cpu_data_t *cpu = (cpu_data_t *)CpuDataEntries[i].cpu_data_vaddr; | |
1315 | npmis += cpu->cpu_monotonic.mtc_npmis; | |
1316 | } | |
1317 | return npmis; | |
1318 | } | |
1319 | ||
d9a64523 | 1320 | static void |
0a7de745 | 1321 | mt_cpu_pmi(cpu_data_t *cpu, uint64_t pmcr0) |
5ba3f43e | 1322 | { |
5ba3f43e A |
1323 | assert(cpu != NULL); |
1324 | assert(ml_get_interrupts_enabled() == FALSE); | |
1325 | ||
ea3f0419 A |
1326 | __builtin_arm_wsr64(PMCR0, PMCR0_INIT); |
1327 | /* | |
1328 | * Ensure the CPMU has flushed any increments at this point, so PMSR is up | |
1329 | * to date. | |
1330 | */ | |
1331 | __builtin_arm_isb(ISB_SY); | |
1332 | ||
cb323159 A |
1333 | cpu->cpu_monotonic.mtc_npmis += 1; |
1334 | cpu->cpu_stat.pmi_cnt_wake += 1; | |
0a7de745 A |
1335 | |
1336 | #if MONOTONIC_DEBUG | |
1337 | if (!PMCR0_PMI(pmcr0)) { | |
1338 | kprintf("monotonic: mt_cpu_pmi but no PMI (PMCR0 = %#llx)\n", | |
1339 | pmcr0); | |
1340 | } | |
1341 | #else /* MONOTONIC_DEBUG */ | |
1342 | #pragma unused(pmcr0) | |
1343 | #endif /* !MONOTONIC_DEBUG */ | |
1344 | ||
1345 | uint64_t pmsr = __builtin_arm_rsr64(PMSR); | |
1346 | ||
1347 | #if MONOTONIC_DEBUG | |
ea3f0419 | 1348 | printf("monotonic: cpu = %d, PMSR = 0x%llx, PMCR0 = 0x%llx\n", |
0a7de745 A |
1349 | cpu_number(), pmsr, pmcr0); |
1350 | #endif /* MONOTONIC_DEBUG */ | |
5ba3f43e | 1351 | |
ea3f0419 A |
1352 | #if MACH_ASSERT |
1353 | uint64_t handled = 0; | |
1354 | #endif /* MACH_ASSERT */ | |
1355 | ||
d9a64523 A |
1356 | /* |
1357 | * monotonic handles any fixed counter PMIs. | |
1358 | */ | |
1359 | for (unsigned int i = 0; i < MT_CORE_NFIXED; i++) { | |
1360 | if ((pmsr & PMSR_OVF(i)) == 0) { | |
1361 | continue; | |
1362 | } | |
1363 | ||
ea3f0419 A |
1364 | #if MACH_ASSERT |
1365 | handled |= 1ULL << i; | |
1366 | #endif /* MACH_ASSERT */ | |
d9a64523 A |
1367 | uint64_t count = mt_cpu_update_count(cpu, i); |
1368 | cpu->cpu_monotonic.mtc_counts[i] += count; | |
1369 | mt_core_set_snap(i, mt_core_reset_values[i]); | |
1370 | cpu->cpu_monotonic.mtc_snaps[i] = mt_core_reset_values[i]; | |
1371 | ||
1372 | if (mt_microstackshots && mt_microstackshot_ctr == i) { | |
1373 | bool user_mode = false; | |
1374 | arm_saved_state_t *state = get_user_regs(current_thread()); | |
1375 | if (state) { | |
1376 | user_mode = PSR64_IS_USER(get_saved_state_cpsr(state)); | |
1377 | } | |
1378 | KDBG_RELEASE(KDBG_EVENTID(DBG_MONOTONIC, DBG_MT_DEBUG, 1), | |
0a7de745 | 1379 | mt_microstackshot_ctr, user_mode); |
d9a64523 | 1380 | mt_microstackshot_pmi_handler(user_mode, mt_microstackshot_ctx); |
ea3f0419 A |
1381 | } else if (mt_debug) { |
1382 | KDBG_RELEASE(KDBG_EVENTID(DBG_MONOTONIC, DBG_MT_DEBUG, 2), | |
1383 | i, count); | |
d9a64523 A |
1384 | } |
1385 | } | |
1386 | ||
1387 | /* | |
1388 | * KPC handles the configurable counter PMIs. | |
1389 | */ | |
1390 | for (unsigned int i = MT_CORE_NFIXED; i < CORE_NCTRS; i++) { | |
5ba3f43e | 1391 | if (pmsr & PMSR_OVF(i)) { |
ea3f0419 A |
1392 | #if MACH_ASSERT |
1393 | handled |= 1ULL << i; | |
1394 | #endif /* MACH_ASSERT */ | |
d9a64523 A |
1395 | extern void kpc_pmi_handler(unsigned int ctr); |
1396 | kpc_pmi_handler(i); | |
5ba3f43e A |
1397 | } |
1398 | } | |
1399 | ||
0a7de745 | 1400 | #if MACH_ASSERT |
ea3f0419 A |
1401 | uint64_t pmsr_after_handling = __builtin_arm_rsr64(PMSR); |
1402 | if (pmsr_after_handling != 0) { | |
1403 | unsigned int first_ctr_ovf = __builtin_ffsll(pmsr_after_handling) - 1; | |
1404 | uint64_t count = 0; | |
1405 | const char *extra = ""; | |
1406 | if (first_ctr_ovf >= CORE_NCTRS) { | |
1407 | extra = " (invalid counter)"; | |
1408 | } else { | |
1409 | count = mt_core_snap(first_ctr_ovf); | |
1410 | } | |
1411 | ||
1412 | panic("monotonic: PMI status not cleared on exit from handler, " | |
1413 | "PMSR = 0x%llx HANDLE -> -> 0x%llx, handled 0x%llx, " | |
1414 | "PMCR0 = 0x%llx, PMC%d = 0x%llx%s", pmsr, pmsr_after_handling, | |
1415 | handled, __builtin_arm_rsr64(PMCR0), first_ctr_ovf, count, extra); | |
1416 | } | |
0a7de745 A |
1417 | #endif /* MACH_ASSERT */ |
1418 | ||
5ba3f43e A |
1419 | core_set_enabled(); |
1420 | } | |
1421 | ||
0a7de745 | 1422 | #if CPMU_AIC_PMI |
5ba3f43e | 1423 | void |
0a7de745 | 1424 | mt_cpmu_aic_pmi(cpu_id_t source) |
d9a64523 | 1425 | { |
0a7de745 A |
1426 | struct cpu_data *curcpu = getCpuDatap(); |
1427 | if (source != curcpu->interrupt_nub) { | |
1428 | panic("monotonic: PMI from IOCPU %p delivered to %p", source, | |
1429 | curcpu->interrupt_nub); | |
1430 | } | |
1431 | mt_cpu_pmi(curcpu, __builtin_arm_rsr64(PMCR0)); | |
1432 | } | |
1433 | #endif /* CPMU_AIC_PMI */ | |
1434 | ||
1435 | void | |
1436 | mt_fiq(void *cpu, uint64_t pmcr0, uint64_t upmsr) | |
1437 | { | |
1438 | #if CPMU_AIC_PMI | |
1439 | #pragma unused(cpu, pmcr0) | |
1440 | #else /* CPMU_AIC_PMI */ | |
1441 | mt_cpu_pmi(cpu, pmcr0); | |
1442 | #endif /* !CPMU_AIC_PMI */ | |
d9a64523 | 1443 | |
c6bf4f31 A |
1444 | #if HAS_UNCORE_CTRS |
1445 | uncore_fiq(upmsr); | |
1446 | #else /* HAS_UNCORE_CTRS */ | |
d9a64523 | 1447 | #pragma unused(upmsr) |
c6bf4f31 | 1448 | #endif /* !HAS_UNCORE_CTRS */ |
d9a64523 A |
1449 | } |
1450 | ||
1451 | static uint32_t mt_xc_sync; | |
1452 | ||
1453 | static void | |
1454 | mt_microstackshot_start_remote(__unused void *arg) | |
1455 | { | |
1456 | cpu_data_t *cpu = getCpuDatap(); | |
1457 | ||
1458 | __builtin_arm_wsr64(PMCR0, PMCR0_INIT); | |
1459 | ||
1460 | for (int i = 0; i < MT_CORE_NFIXED; i++) { | |
1461 | uint64_t count = mt_cpu_update_count(cpu, i); | |
1462 | cpu->cpu_monotonic.mtc_counts[i] += count; | |
1463 | mt_core_set_snap(i, mt_core_reset_values[i]); | |
1464 | cpu->cpu_monotonic.mtc_snaps[i] = mt_core_reset_values[i]; | |
1465 | } | |
1466 | ||
1467 | core_set_enabled(); | |
1468 | ||
cb323159 | 1469 | if (os_atomic_dec(&mt_xc_sync, relaxed) == 0) { |
d9a64523 A |
1470 | thread_wakeup((event_t)&mt_xc_sync); |
1471 | } | |
1472 | } | |
1473 | ||
1474 | int | |
1475 | mt_microstackshot_start_arch(uint64_t period) | |
5ba3f43e | 1476 | { |
0a7de745 A |
1477 | uint64_t reset_value = 0; |
1478 | int ovf = os_sub_overflow(CTR_MAX, period, &reset_value); | |
1479 | if (ovf) { | |
1480 | return ERANGE; | |
1481 | } | |
1482 | ||
1483 | mt_core_reset_values[mt_microstackshot_ctr] = reset_value; | |
d9a64523 | 1484 | cpu_broadcast_xcall(&mt_xc_sync, TRUE, mt_microstackshot_start_remote, |
0a7de745 | 1485 | mt_microstackshot_start_remote /* cannot pass NULL */); |
d9a64523 | 1486 | return 0; |
5ba3f43e A |
1487 | } |
1488 | ||
1489 | #pragma mark dev nodes | |
1490 | ||
d9a64523 | 1491 | struct mt_device mt_devices[] = { |
5ba3f43e | 1492 | [0] = { |
d9a64523 | 1493 | .mtd_name = "core", |
5ba3f43e A |
1494 | .mtd_init = core_init, |
1495 | }, | |
c6bf4f31 A |
1496 | #if HAS_UNCORE_CTRS |
1497 | [1] = { | |
1498 | .mtd_name = "uncore", | |
1499 | .mtd_init = uncore_init, | |
1500 | .mtd_add = uncore_add, | |
1501 | .mtd_reset = uncore_reset, | |
1502 | .mtd_enable = uncore_set_enabled, | |
1503 | .mtd_read = uncore_read, | |
1504 | ||
1505 | .mtd_nmonitors = UNCORE_NMONITORS, | |
1506 | .mtd_ncounters = UNCORE_NCTRS, | |
1507 | } | |
1508 | #endif /* HAS_UNCORE_CTRS */ | |
5ba3f43e A |
1509 | }; |
1510 | ||
1511 | static_assert( | |
0a7de745 A |
1512 | (sizeof(mt_devices) / sizeof(mt_devices[0])) == MT_NDEVS, |
1513 | "MT_NDEVS macro should be same as the length of mt_devices"); |