Skip to content
Permalink
main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
/*
* Copyright (c) 2017-2020 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. The rights granted to you under the License
* may not be used to create, or enable the creation or redistribution of,
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
*
* Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
#include <arm/cpu_data_internal.h>
#include <arm/machine_routines.h>
#include <arm64/monotonic.h>
#include <kern/assert.h>
#include <kern/debug.h> /* panic */
#include <kern/kpc.h>
#include <kern/monotonic.h>
#include <machine/atomic.h>
#include <machine/limits.h> /* CHAR_BIT */
#include <os/overflow.h>
#include <pexpert/arm64/board_config.h>
#include <pexpert/device_tree.h> /* SecureDTFindEntry */
#include <pexpert/pexpert.h>
#include <stdatomic.h>
#include <stdint.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/monotonic.h>
/*
* Ensure that control registers read back what was written under MACH_ASSERT
* kernels.
*
* A static inline function cannot be used due to passing the register through
* the builtin -- it requires a constant string as its first argument, since
* MSRs registers are encoded as an immediate in the instruction.
*/
#if MACH_ASSERT
#define CTRL_REG_SET(reg, val) do { \
__builtin_arm_wsr64((reg), (val)); \
uint64_t __check_reg = __builtin_arm_rsr64((reg)); \
if (__check_reg != (val)) { \
panic("value written to %s was not read back (wrote %llx, read %llx)", \
#reg, (val), __check_reg); \
} \
} while (0)
#else /* MACH_ASSERT */
#define CTRL_REG_SET(reg, val) __builtin_arm_wsr64((reg), (val))
#endif /* MACH_ASSERT */
#pragma mark core counters
bool mt_core_supported = true;
static const ml_topology_info_t *topology_info;
/*
* PMC[0-1] are the 48-bit fixed counters -- PMC0 is cycles and PMC1 is
* instructions (see arm64/monotonic.h).
*
* PMC2+ are currently handled by kpc.
*/
#define PMC_0_7(X, A) X(0, A); X(1, A); X(2, A); X(3, A); X(4, A); X(5, A); \
X(6, A); X(7, A)
#if CORE_NCTRS > 8
#define PMC_8_9(X, A) X(8, A); X(9, A)
#else // CORE_NCTRS > 8
#define PMC_8_9(X, A)
#endif // CORE_NCTRS > 8
#define PMC_ALL(X, A) PMC_0_7(X, A); PMC_8_9(X, A)
#define CTR_MAX ((UINT64_C(1) << 47) - 1)
#define CYCLES 0
#define INSTRS 1
/*
* PMC0's offset into a core's PIO range.
*
* This allows cores to remotely query another core's counters.
*/
#define PIO_PMC0_OFFSET (0x200)
/*
* The offset of the counter in the configuration registers. Post-Hurricane
* devices have additional counters that need a larger shift than the original
* counters.
*
* XXX For now, just support the lower-numbered counters.
*/
#define CTR_POS(CTR) (CTR)
/*
* PMCR0 is the main control register for the performance monitor. It
* controls whether the counters are enabled, how they deliver interrupts, and
* other features.
*/
#define PMCR0_CTR_EN(CTR) (UINT64_C(1) << CTR_POS(CTR))
#define PMCR0_FIXED_EN (PMCR0_CTR_EN(CYCLES) | PMCR0_CTR_EN(INSTRS))
/* how interrupts are delivered on a PMI */
enum {
PMCR0_INTGEN_OFF = 0,
PMCR0_INTGEN_PMI = 1,
PMCR0_INTGEN_AIC = 2,
PMCR0_INTGEN_HALT = 3,
PMCR0_INTGEN_FIQ = 4,
};
#define PMCR0_INTGEN_SET(X) ((uint64_t)(X) << 8)
#if CPMU_AIC_PMI
#define PMCR0_INTGEN_INIT PMCR0_INTGEN_SET(PMCR0_INTGEN_AIC)
#else /* CPMU_AIC_PMI */
#define PMCR0_INTGEN_INIT PMCR0_INTGEN_SET(PMCR0_INTGEN_FIQ)
#endif /* !CPMU_AIC_PMI */
#define PMCR0_PMI_SHIFT (12)
#define PMCR0_CTR_GE8_PMI_SHIFT (44)
#define PMCR0_PMI_EN(CTR) (UINT64_C(1) << (PMCR0_PMI_SHIFT + CTR_POS(CTR)))
/* fixed counters are always counting */
#define PMCR0_PMI_INIT (PMCR0_PMI_EN(CYCLES) | PMCR0_PMI_EN(INSTRS))
/* disable counting on a PMI */
#define PMCR0_DISCNT_EN (UINT64_C(1) << 20)
/* block PMIs until ERET retires */
#define PMCR0_WFRFE_EN (UINT64_C(1) << 22)
/* count global (not just core-local) L2C events */
#define PMCR0_L2CGLOBAL_EN (UINT64_C(1) << 23)
/* user mode access to configuration registers */
#define PMCR0_USEREN_EN (UINT64_C(1) << 30)
#define PMCR0_CTR_GE8_EN_SHIFT (32)
#define PMCR0_INIT (PMCR0_INTGEN_INIT | PMCR0_PMI_INIT)
/*
* PMCR1 controls which execution modes count events.
*/
#define PMCR1_EL0A32_EN(CTR) (UINT64_C(1) << (0 + CTR_POS(CTR)))
#define PMCR1_EL0A64_EN(CTR) (UINT64_C(1) << (8 + CTR_POS(CTR)))
#define PMCR1_EL1A64_EN(CTR) (UINT64_C(1) << (16 + CTR_POS(CTR)))
/* PMCR1_EL3A64 is not supported on systems with no monitor */
#if defined(APPLEHURRICANE)
#define PMCR1_EL3A64_EN(CTR) UINT64_C(0)
#else
#define PMCR1_EL3A64_EN(CTR) (UINT64_C(1) << (24 + CTR_POS(CTR)))
#endif
#define PMCR1_ALL_EN(CTR) (PMCR1_EL0A32_EN(CTR) | PMCR1_EL0A64_EN(CTR) | \
PMCR1_EL1A64_EN(CTR) | PMCR1_EL3A64_EN(CTR))
/* fixed counters always count in all modes */
#define PMCR1_INIT (PMCR1_ALL_EN(CYCLES) | PMCR1_ALL_EN(INSTRS))
static inline void
core_init_execution_modes(void)
{
uint64_t pmcr1;
pmcr1 = __builtin_arm_rsr64("PMCR1_EL1");
pmcr1 |= PMCR1_INIT;
__builtin_arm_wsr64("PMCR1_EL1", pmcr1);
}
#define PMSR_OVF(CTR) (1ULL << (CTR))
static int
core_init(__unused mt_device_t dev)
{
/* the dev node interface to the core counters is still unsupported */
return ENOTSUP;
}
struct mt_cpu *
mt_cur_cpu(void)
{
return &getCpuDatap()->cpu_monotonic;
}
uint64_t
mt_core_snap(unsigned int ctr)
{
switch (ctr) {
#define PMC_RD(CTR, UNUSED) case (CTR): return __builtin_arm_rsr64(__MSR_STR(PMC ## CTR))
PMC_ALL(PMC_RD, 0);
#undef PMC_RD
default:
panic("monotonic: invalid core counter read: %u", ctr);
__builtin_unreachable();
}
}
void
mt_core_set_snap(unsigned int ctr, uint64_t count)
{
switch (ctr) {
case 0:
__builtin_arm_wsr64("PMC0", count);
break;
case 1:
__builtin_arm_wsr64("PMC1", count);
break;
default:
panic("monotonic: invalid core counter %u write %llu", ctr, count);
__builtin_unreachable();
}
}
static void
core_set_enabled(void)
{
uint64_t pmcr0 = __builtin_arm_rsr64("PMCR0_EL1");
pmcr0 |= PMCR0_INIT | PMCR0_FIXED_EN;
if (kpc_get_running() & KPC_CLASS_CONFIGURABLE_MASK) {
uint64_t kpc_ctrs = kpc_get_configurable_pmc_mask(
KPC_CLASS_CONFIGURABLE_MASK) << MT_CORE_NFIXED;
#if KPC_ARM64_CONFIGURABLE_COUNT > 6
uint64_t ctrs_ge8 = kpc_ctrs >> 8;
pmcr0 |= ctrs_ge8 << PMCR0_CTR_GE8_EN_SHIFT;
pmcr0 |= ctrs_ge8 << PMCR0_CTR_GE8_PMI_SHIFT;
kpc_ctrs &= (1ULL << 8) - 1;
#endif /* KPC_ARM64_CONFIGURABLE_COUNT > 6 */
kpc_ctrs |= kpc_ctrs << PMCR0_PMI_SHIFT;
pmcr0 |= kpc_ctrs;
}
__builtin_arm_wsr64("PMCR0_EL1", pmcr0);
#if MACH_ASSERT
/*
* Only check for the values that were ORed in.
*/
uint64_t pmcr0_check = __builtin_arm_rsr64("PMCR0_EL1");
if ((pmcr0_check & (PMCR0_INIT | PMCR0_FIXED_EN)) != (PMCR0_INIT | PMCR0_FIXED_EN)) {
panic("monotonic: hardware ignored enable (read %llx, wrote %llx)",
pmcr0_check, pmcr0);
}
#endif /* MACH_ASSERT */
}
static void
core_idle(__unused cpu_data_t *cpu)
{
assert(cpu != NULL);
assert(ml_get_interrupts_enabled() == FALSE);
#if DEBUG
uint64_t pmcr0 = __builtin_arm_rsr64("PMCR0_EL1");
if ((pmcr0 & PMCR0_FIXED_EN) == 0) {
panic("monotonic: counters disabled before idling, pmcr0 = 0x%llx\n", pmcr0);
}
uint64_t pmcr1 = __builtin_arm_rsr64("PMCR1_EL1");
if ((pmcr1 & PMCR1_INIT) == 0) {
panic("monotonic: counter modes disabled before idling, pmcr1 = 0x%llx\n", pmcr1);
}
#endif /* DEBUG */
/* disable counters before updating */
__builtin_arm_wsr64("PMCR0_EL1", PMCR0_INIT);
mt_update_fixed_counts();
}
#pragma mark uncore performance monitor
#if HAS_UNCORE_CTRS
static bool mt_uncore_initted = false;
/*
* Uncore Performance Monitor
*
* Uncore performance monitors provide event-counting for the last-level caches
* (LLCs). Each LLC has its own uncore performance monitor, which can only be
* accessed by cores that use that LLC. Like the core performance monitoring
* unit, uncore counters are configured globally. If there is more than one
* LLC on the system, PIO reads must be used to satisfy uncore requests (using
* the `_r` remote variants of the access functions). Otherwise, local MSRs
* suffice (using the `_l` local variants of the access functions).
*/
#if UNCORE_PER_CLUSTER
#define MAX_NMONITORS MAX_CPU_CLUSTERS
static uintptr_t cpm_impl[MAX_NMONITORS] = {};
#else
#define MAX_NMONITORS (1)
#endif /* UNCORE_PER_CLUSTER */
#if UNCORE_VERSION >= 2
/*
* V2 uncore monitors feature a CTI mechanism -- the second bit of UPMSR is
* used to track if a CTI has been triggered due to an overflow.
*/
#define UPMSR_OVF_POS 2
#else /* UNCORE_VERSION >= 2 */
#define UPMSR_OVF_POS 1
#endif /* UNCORE_VERSION < 2 */
#define UPMSR_OVF(R, CTR) ((R) >> ((CTR) + UPMSR_OVF_POS) & 0x1)
#define UPMSR_OVF_MASK (((UINT64_C(1) << UNCORE_NCTRS) - 1) << UPMSR_OVF_POS)
#define UPMPCM_CORE(ID) (UINT64_C(1) << (ID))
/*
* The uncore_pmi_mask is a bitmask of CPUs that receive uncore PMIs. It's
* initialized by uncore_init and controllable by the uncore_pmi_mask boot-arg.
*/
static int32_t uncore_pmi_mask = 0;
/*
* The uncore_active_ctrs is a bitmask of uncore counters that are currently
* requested.
*/
static uint16_t uncore_active_ctrs = 0;
static_assert(sizeof(uncore_active_ctrs) * CHAR_BIT >= UNCORE_NCTRS,
"counter mask should fit the full range of counters");
/*
* mt_uncore_enabled is true when any uncore counters are active.
*/
bool mt_uncore_enabled = false;
/*
* The uncore_events are the event configurations for each uncore counter -- as
* a union to make it easy to program the hardware registers.
*/
static struct uncore_config {
union {
uint8_t uce_ctrs[UNCORE_NCTRS];
uint64_t uce_regs[UNCORE_NCTRS / 8];
} uc_events;
union {
uint16_t uccm_masks[UNCORE_NCTRS];
uint64_t uccm_regs[UNCORE_NCTRS / 4];
} uc_cpu_masks[MAX_NMONITORS];
} uncore_config;
static struct uncore_monitor {
/*
* The last snapshot of each of the hardware counter values.
*/
uint64_t um_snaps[UNCORE_NCTRS];
/*
* The accumulated counts for each counter.
*/
uint64_t um_counts[UNCORE_NCTRS];
/*
* Protects accessing the hardware registers and fields in this structure.
*/
lck_spin_t um_lock;
/*
* Whether this monitor needs its registers restored after wake.
*/
bool um_sleeping;
} uncore_monitors[MAX_NMONITORS];
/*
* Each uncore unit has its own monitor, corresponding to the memory hierarchy
* of the LLCs.
*/
static unsigned int
uncore_nmonitors(void)
{
#if UNCORE_PER_CLUSTER
return topology_info->num_clusters;
#else /* UNCORE_PER_CLUSTER */
return 1;
#endif /* !UNCORE_PER_CLUSTER */
}
static unsigned int
uncmon_get_curid(void)
{
#if UNCORE_PER_CLUSTER
// Pointer arithmetic to translate cluster_id into a clusters[] index.
return cpu_cluster_id();
#else /* UNCORE_PER_CLUSTER */
return 0;
#endif /* !UNCORE_PER_CLUSTER */
}
/*
* Per-monitor locks are required to prevent races with the PMI handlers, not
* from other CPUs that are configuring (those are serialized with monotonic's
* per-device lock).
*/
static int
uncmon_lock(struct uncore_monitor *mon)
{
int intrs_en = ml_set_interrupts_enabled(FALSE);
lck_spin_lock(&mon->um_lock);
return intrs_en;
}
static void
uncmon_unlock(struct uncore_monitor *mon, int intrs_en)
{
lck_spin_unlock(&mon->um_lock);
(void)ml_set_interrupts_enabled(intrs_en);
}
/*
* Helper functions for accessing the hardware -- these require the monitor be
* locked to prevent other CPUs' PMI handlers from making local modifications
* or updating the counts.
*/
#if UNCORE_VERSION >= 2
#define UPMCR0_INTEN_POS 20
#define UPMCR0_INTGEN_POS 16
#else /* UNCORE_VERSION >= 2 */
#define UPMCR0_INTEN_POS 12
#define UPMCR0_INTGEN_POS 8
#endif /* UNCORE_VERSION < 2 */
enum {
UPMCR0_INTGEN_OFF = 0,
/* fast PMIs are only supported on core CPMU */
UPMCR0_INTGEN_AIC = 2,
UPMCR0_INTGEN_HALT = 3,
UPMCR0_INTGEN_FIQ = 4,
};
/* always enable interrupts for all counters */
#define UPMCR0_INTEN (((1ULL << UNCORE_NCTRS) - 1) << UPMCR0_INTEN_POS)
/* route uncore PMIs through the FIQ path */
#define UPMCR0_INIT (UPMCR0_INTEN | (UPMCR0_INTGEN_FIQ << UPMCR0_INTGEN_POS))
/*
* Turn counting on for counters set in the `enctrmask` and off, otherwise.
*/
static inline void
uncmon_set_counting_locked_l(__unused unsigned int monid, uint64_t enctrmask)
{
/*
* UPMCR0 controls which counters are enabled and how interrupts are generated
* for overflows.
*/
__builtin_arm_wsr64("UPMCR0_EL1", UPMCR0_INIT | enctrmask);
}
#if UNCORE_PER_CLUSTER
/*
* Turn counting on for counters set in the `enctrmask` and off, otherwise.
*/
static inline void
uncmon_set_counting_locked_r(unsigned int monid, uint64_t enctrmask)
{
const uintptr_t upmcr0_offset = 0x4180;
*(uint64_t *)(cpm_impl[monid] + upmcr0_offset) = UPMCR0_INIT | enctrmask;
}
#endif /* UNCORE_PER_CLUSTER */
/*
* The uncore performance monitoring counters (UPMCs) are 48-bits wide. The
* high bit is an overflow bit, triggering a PMI, providing 47 usable bits.
*/
#define UPMC_MAX ((UINT64_C(1) << 48) - 1)
/*
* The `__builtin_arm_{r,w}sr` functions require constant strings, since the
* MSR/MRS instructions encode the registers as immediates. Otherwise, this
* would be indexing into an array of strings.
*/
#define UPMC_0_7(X, A) X(0, A); X(1, A); X(2, A); X(3, A); X(4, A); X(5, A); \
X(6, A); X(7, A)
#if UNCORE_NCTRS <= 8
#define UPMC_ALL(X, A) UPMC_0_7(X, A)
#else /* UNCORE_NCTRS <= 8 */
#define UPMC_8_15(X, A) X(8, A); X(9, A); X(10, A); X(11, A); X(12, A); \
X(13, A); X(14, A); X(15, A)
#define UPMC_ALL(X, A) UPMC_0_7(X, A); UPMC_8_15(X, A)
#endif /* UNCORE_NCTRS > 8 */
static inline uint64_t
uncmon_read_counter_locked_l(__unused unsigned int monid, unsigned int ctr)
{
assert(ctr < UNCORE_NCTRS);
switch (ctr) {
#define UPMC_RD(CTR, UNUSED) case (CTR): return __builtin_arm_rsr64(__MSR_STR(UPMC ## CTR))
UPMC_ALL(UPMC_RD, 0);
#undef UPMC_RD
default:
panic("monotonic: invalid counter read %u", ctr);
__builtin_unreachable();
}
}
static inline void
uncmon_write_counter_locked_l(__unused unsigned int monid, unsigned int ctr,
uint64_t count)
{
assert(count < UPMC_MAX);
assert(ctr < UNCORE_NCTRS);
switch (ctr) {
#define UPMC_WR(CTR, COUNT) case (CTR): \
return __builtin_arm_wsr64(__MSR_STR(UPMC ## CTR), (COUNT))
UPMC_ALL(UPMC_WR, count);
#undef UPMC_WR
default:
panic("monotonic: invalid counter write %u", ctr);
}
}
#if UNCORE_PER_CLUSTER
uintptr_t upmc_offs[UNCORE_NCTRS] = {
[0] = 0x4100, [1] = 0x4248, [2] = 0x4110, [3] = 0x4250, [4] = 0x4120,
[5] = 0x4258, [6] = 0x4130, [7] = 0x4260, [8] = 0x4140, [9] = 0x4268,
[10] = 0x4150, [11] = 0x4270, [12] = 0x4160, [13] = 0x4278,
[14] = 0x4170, [15] = 0x4280,
};
static inline uint64_t
uncmon_read_counter_locked_r(unsigned int mon_id, unsigned int ctr)
{
assert(mon_id < uncore_nmonitors());
assert(ctr < UNCORE_NCTRS);
return *(uint64_t *)(cpm_impl[mon_id] + upmc_offs[ctr]);
}
static inline void
uncmon_write_counter_locked_r(unsigned int mon_id, unsigned int ctr,
uint64_t count)
{
assert(count < UPMC_MAX);
assert(ctr < UNCORE_NCTRS);
assert(mon_id < uncore_nmonitors());
*(uint64_t *)(cpm_impl[mon_id] + upmc_offs[ctr]) = count;
}
#endif /* UNCORE_PER_CLUSTER */
static inline void
uncmon_update_locked(unsigned int monid, unsigned int curid, unsigned int ctr)
{
struct uncore_monitor *mon = &uncore_monitors[monid];
uint64_t snap = 0;
if (curid == monid) {
snap = uncmon_read_counter_locked_l(monid, ctr);
} else {
#if UNCORE_PER_CLUSTER
snap = uncmon_read_counter_locked_r(monid, ctr);
#endif /* UNCORE_PER_CLUSTER */
}
/* counters should increase monotonically */
assert(snap >= mon->um_snaps[ctr]);
mon->um_counts[ctr] += snap - mon->um_snaps[ctr];
mon->um_snaps[ctr] = snap;
}
static inline void
uncmon_program_events_locked_l(unsigned int monid)
{
/*
* UPMESR[01] is the event selection register that determines which event a
* counter will count.
*/
CTRL_REG_SET("UPMESR0_EL1", uncore_config.uc_events.uce_regs[0]);
#if UNCORE_NCTRS > 8
CTRL_REG_SET("UPMESR1_EL1", uncore_config.uc_events.uce_regs[1]);
#endif /* UNCORE_NCTRS > 8 */
/*
* UPMECM[0123] are the event core masks for each counter -- whether or not
* that counter counts events generated by an agent. These are set to all
* ones so the uncore counters count events from all cores.
*
* The bits are based off the start of the cluster -- e.g. even if a core
* has a CPU ID of 4, it might be the first CPU in a cluster. Shift the
* registers right by the ID of the first CPU in the cluster.
*/
CTRL_REG_SET("UPMECM0_EL1",
uncore_config.uc_cpu_masks[monid].uccm_regs[0]);
CTRL_REG_SET("UPMECM1_EL1",
uncore_config.uc_cpu_masks[monid].uccm_regs[1]);
#if UNCORE_NCTRS > 8
CTRL_REG_SET("UPMECM2_EL1",
uncore_config.uc_cpu_masks[monid].uccm_regs[2]);
CTRL_REG_SET("UPMECM3_EL1",
uncore_config.uc_cpu_masks[monid].uccm_regs[3]);
#endif /* UNCORE_NCTRS > 8 */
}
#if UNCORE_PER_CLUSTER
static inline void
uncmon_program_events_locked_r(unsigned int monid)
{
const uintptr_t upmesr_offs[2] = {[0] = 0x41b0, [1] = 0x41b8, };
for (unsigned int i = 0; i < sizeof(upmesr_offs) / sizeof(upmesr_offs[0]);
i++) {
*(uint64_t *)(cpm_impl[monid] + upmesr_offs[i]) =
uncore_config.uc_events.uce_regs[i];
}
const uintptr_t upmecm_offs[4] = {
[0] = 0x4190, [1] = 0x4198, [2] = 0x41a0, [3] = 0x41a8,
};
for (unsigned int i = 0; i < sizeof(upmecm_offs) / sizeof(upmecm_offs[0]);
i++) {
*(uint64_t *)(cpm_impl[monid] + upmecm_offs[i]) =
uncore_config.uc_cpu_masks[monid].uccm_regs[i];
}
}
#endif /* UNCORE_PER_CLUSTER */
static void
uncmon_clear_int_locked_l(__unused unsigned int monid)
{
__builtin_arm_wsr64("UPMSR_EL1", 0);
}
#if UNCORE_PER_CLUSTER
static void
uncmon_clear_int_locked_r(unsigned int monid)
{
const uintptr_t upmsr_off = 0x41c0;
*(uint64_t *)(cpm_impl[monid] + upmsr_off) = 0;
}
#endif /* UNCORE_PER_CLUSTER */
/*
* Get the PMI mask for the provided `monid` -- that is, the bitmap of CPUs
* that should be sent PMIs for a particular monitor.
*/
static uint64_t
uncmon_get_pmi_mask(unsigned int monid)
{
uint64_t pmi_mask = uncore_pmi_mask;
#if UNCORE_PER_CLUSTER
pmi_mask &= topology_info->clusters[monid].cpu_mask;
#else /* UNCORE_PER_CLUSTER */
#pragma unused(monid)
#endif /* !UNCORE_PER_CLUSTER */
return pmi_mask;
}
/*
* Initialization routines for the uncore counters.
*/
static void
uncmon_init_locked_l(unsigned int monid)
{
/*
* UPMPCM defines the PMI core mask for the UPMCs -- which cores should
* receive interrupts on overflow.
*/
CTRL_REG_SET("UPMPCM_EL1", uncmon_get_pmi_mask(monid));
uncmon_set_counting_locked_l(monid,
mt_uncore_enabled ? uncore_active_ctrs : 0);
}
#if UNCORE_PER_CLUSTER
static uintptr_t acc_impl[MAX_NMONITORS] = {};
static void
uncmon_init_locked_r(unsigned int monid)
{
const uintptr_t upmpcm_off = 0x1010;
*(uint64_t *)(acc_impl[monid] + upmpcm_off) = uncmon_get_pmi_mask(monid);
uncmon_set_counting_locked_r(monid,
mt_uncore_enabled ? uncore_active_ctrs : 0);
}
#endif /* UNCORE_PER_CLUSTER */
/*
* Initialize the uncore device for monotonic.
*/
static int
uncore_init(__unused mt_device_t dev)
{
#if HAS_UNCORE_CTRS
assert(MT_NDEVS > 0);
mt_devices[MT_NDEVS - 1].mtd_nmonitors = (uint8_t)uncore_nmonitors();
#endif
#if DEVELOPMENT || DEBUG
/*
* Development and debug kernels observe the `uncore_pmi_mask` boot-arg,
* allowing PMIs to be routed to the CPUs present in the supplied bitmap.
* Do some sanity checks on the value provided.
*/
bool parsed_arg = PE_parse_boot_argn("uncore_pmi_mask", &uncore_pmi_mask,
sizeof(uncore_pmi_mask));
if (parsed_arg) {
#if UNCORE_PER_CLUSTER
if (__builtin_popcount(uncore_pmi_mask) != (int)uncore_nmonitors()) {
panic("monotonic: invalid uncore PMI mask 0x%x", uncore_pmi_mask);
}
for (unsigned int i = 0; i < uncore_nmonitors(); i++) {
if (__builtin_popcountll(uncmon_get_pmi_mask(i)) != 1) {
panic("monotonic: invalid uncore PMI CPU for cluster %d in mask 0x%x",
i, uncore_pmi_mask);
}
}
#else /* UNCORE_PER_CLUSTER */
if (__builtin_popcount(uncore_pmi_mask) != 1) {
panic("monotonic: invalid uncore PMI mask 0x%x", uncore_pmi_mask);
}
#endif /* !UNCORE_PER_CLUSTER */
} else
#endif /* DEVELOPMENT || DEBUG */
{
#if UNCORE_PER_CLUSTER
for (unsigned int i = 0; i < topology_info->num_clusters; i++) {
uncore_pmi_mask |= 1ULL << topology_info->clusters[i].first_cpu_id;
}
#else /* UNCORE_PER_CLUSTER */
/* arbitrarily route to core 0 */
uncore_pmi_mask |= 1;
#endif /* !UNCORE_PER_CLUSTER */
}
assert(uncore_pmi_mask != 0);
unsigned int curmonid = uncmon_get_curid();
for (unsigned int monid = 0; monid < uncore_nmonitors(); monid++) {
#if UNCORE_PER_CLUSTER
ml_topology_cluster_t *cluster = &topology_info->clusters[monid];
cpm_impl[monid] = (uintptr_t)cluster->cpm_IMPL_regs;
acc_impl[monid] = (uintptr_t)cluster->acc_IMPL_regs;
assert(cpm_impl[monid] != 0 && acc_impl[monid] != 0);
#endif /* UNCORE_PER_CLUSTER */
struct uncore_monitor *mon = &uncore_monitors[monid];
lck_spin_init(&mon->um_lock, &mt_lock_grp, LCK_ATTR_NULL);
int intrs_en = uncmon_lock(mon);
if (monid != curmonid) {
#if UNCORE_PER_CLUSTER
uncmon_init_locked_r(monid);
#endif /* UNCORE_PER_CLUSTER */
} else {
uncmon_init_locked_l(monid);
}
uncmon_unlock(mon, intrs_en);
}
mt_uncore_initted = true;
return 0;
}
/*
* Support for monotonic's mtd_read function.
*/
static void
uncmon_read_all_counters(unsigned int monid, unsigned int curmonid,
uint64_t ctr_mask, uint64_t *counts)
{
struct uncore_monitor *mon = &uncore_monitors[monid];
int intrs_en = uncmon_lock(mon);
for (unsigned int ctr = 0; ctr < UNCORE_NCTRS; ctr++) {
if (ctr_mask & (1ULL << ctr)) {
uncmon_update_locked(monid, curmonid, ctr);
counts[ctr] = mon->um_counts[ctr];
}
}
uncmon_unlock(mon, intrs_en);
}
/*
* Read all monitor's counters.
*/
static int
uncore_read(uint64_t ctr_mask, uint64_t *counts_out)
{
assert(ctr_mask != 0);
assert(counts_out != NULL);
if (!uncore_active_ctrs) {
return EPWROFF;
}
if (ctr_mask & ~uncore_active_ctrs) {
return EINVAL;
}
unsigned int curmonid = uncmon_get_curid();
for (unsigned int monid = 0; monid < uncore_nmonitors(); monid++) {
/*
* Find this monitor's starting offset into the `counts_out` array.
*/
uint64_t *counts = counts_out + (UNCORE_NCTRS * monid);
uncmon_read_all_counters(monid, curmonid, ctr_mask, counts);
}
return 0;
}
/*
* Support for monotonic's mtd_add function.
*/
/*
* Add an event to the current uncore configuration. This doesn't take effect
* until the counters are enabled again, so there's no need to involve the
* monitors.
*/
static int
uncore_add(struct monotonic_config *config, uint32_t *ctr_out)
{
if (mt_uncore_enabled) {
return EBUSY;
}
uint32_t available = ~uncore_active_ctrs & config->allowed_ctr_mask;
if (available == 0) {
return ENOSPC;
}
uint32_t valid_ctrs = (UINT32_C(1) << UNCORE_NCTRS) - 1;
if ((available & valid_ctrs) == 0) {
return E2BIG;
}
uint32_t ctr = __builtin_ffsll(available) - 1;
uncore_active_ctrs |= UINT64_C(1) << ctr;
uncore_config.uc_events.uce_ctrs[ctr] = (uint8_t)config->event;
uint64_t cpu_mask = UINT64_MAX;
if (config->cpu_mask != 0) {
cpu_mask = config->cpu_mask;
}
for (unsigned int i = 0; i < uncore_nmonitors(); i++) {
#if UNCORE_PER_CLUSTER
const unsigned int shift = topology_info->clusters[i].first_cpu_id;
#else /* UNCORE_PER_CLUSTER */
const unsigned int shift = 0;
#endif /* !UNCORE_PER_CLUSTER */
uncore_config.uc_cpu_masks[i].uccm_masks[ctr] = (uint16_t)(cpu_mask >> shift);
}
*ctr_out = ctr;
return 0;
}
/*
* Support for monotonic's mtd_reset function.
*/
/*
* Reset all configuration and disable the counters if they're currently
* counting.
*/
static void
uncore_reset(void)
{
mt_uncore_enabled = false;
unsigned int curmonid = uncmon_get_curid();
for (unsigned int monid = 0; monid < uncore_nmonitors(); monid++) {
struct uncore_monitor *mon = &uncore_monitors[monid];
bool remote = monid != curmonid;
int intrs_en = uncmon_lock(mon);
if (remote) {
#if UNCORE_PER_CLUSTER
uncmon_set_counting_locked_r(monid, 0);
#endif /* UNCORE_PER_CLUSTER */
} else {
uncmon_set_counting_locked_l(monid, 0);
}
for (int ctr = 0; ctr < UNCORE_NCTRS; ctr++) {
if (uncore_active_ctrs & (1U << ctr)) {
if (remote) {
#if UNCORE_PER_CLUSTER
uncmon_write_counter_locked_r(monid, ctr, 0);
#endif /* UNCORE_PER_CLUSTER */
} else {
uncmon_write_counter_locked_l(monid, ctr, 0);
}
}
}
memset(&mon->um_snaps, 0, sizeof(mon->um_snaps));
memset(&mon->um_counts, 0, sizeof(mon->um_counts));
if (remote) {
#if UNCORE_PER_CLUSTER
uncmon_clear_int_locked_r(monid);
#endif /* UNCORE_PER_CLUSTER */
} else {
uncmon_clear_int_locked_l(monid);
}
uncmon_unlock(mon, intrs_en);
}
uncore_active_ctrs = 0;
memset(&uncore_config, 0, sizeof(uncore_config));
for (unsigned int monid = 0; monid < uncore_nmonitors(); monid++) {
struct uncore_monitor *mon = &uncore_monitors[monid];
bool remote = monid != curmonid;
int intrs_en = uncmon_lock(mon);
if (remote) {
#if UNCORE_PER_CLUSTER
uncmon_program_events_locked_r(monid);
#endif /* UNCORE_PER_CLUSTER */
} else {
uncmon_program_events_locked_l(monid);
}
uncmon_unlock(mon, intrs_en);
}
}
/*
* Support for monotonic's mtd_enable function.
*/
static void
uncmon_set_enabled_l(unsigned int monid, bool enable)
{
struct uncore_monitor *mon = &uncore_monitors[monid];
int intrs_en = uncmon_lock(mon);
if (enable) {
uncmon_program_events_locked_l(monid);
uncmon_set_counting_locked_l(monid, uncore_active_ctrs);
} else {
uncmon_set_counting_locked_l(monid, 0);
}
uncmon_unlock(mon, intrs_en);
}
#if UNCORE_PER_CLUSTER
static void
uncmon_set_enabled_r(unsigned int monid, bool enable)
{
struct uncore_monitor *mon = &uncore_monitors[monid];
int intrs_en = uncmon_lock(mon);
if (enable) {
uncmon_program_events_locked_r(monid);
uncmon_set_counting_locked_r(monid, uncore_active_ctrs);
} else {
uncmon_set_counting_locked_r(monid, 0);
}
uncmon_unlock(mon, intrs_en);
}
#endif /* UNCORE_PER_CLUSTER */
static void
uncore_set_enabled(bool enable)
{
mt_uncore_enabled = enable;
unsigned int curmonid = uncmon_get_curid();
for (unsigned int monid = 0; monid < uncore_nmonitors(); monid++) {
if (monid != curmonid) {
#if UNCORE_PER_CLUSTER
uncmon_set_enabled_r(monid, enable);
#endif /* UNCORE_PER_CLUSTER */
} else {
uncmon_set_enabled_l(monid, enable);
}
}
}
/*
* Hooks in the machine layer.
*/
static void
uncore_fiq(uint64_t upmsr)
{
/*
* Determine which counters overflowed.
*/
uint64_t disable_ctr_mask = (upmsr & UPMSR_OVF_MASK) >> UPMSR_OVF_POS;
/* should not receive interrupts from inactive counters */
assert(!(disable_ctr_mask & ~uncore_active_ctrs));
unsigned int monid = uncmon_get_curid();
struct uncore_monitor *mon = &uncore_monitors[monid];
int intrs_en = uncmon_lock(mon);
/*
* Disable any counters that overflowed.
*/
uncmon_set_counting_locked_l(monid,
uncore_active_ctrs & ~disable_ctr_mask);
/*
* With the overflowing counters disabled, capture their counts and reset
* the UPMCs and their snapshots to 0.
*/
for (unsigned int ctr = 0; ctr < UNCORE_NCTRS; ctr++) {
if (UPMSR_OVF(upmsr, ctr)) {
uncmon_update_locked(monid, monid, ctr);
mon->um_snaps[ctr] = 0;
uncmon_write_counter_locked_l(monid, ctr, 0);
}
}
/*
* Acknowledge the interrupt, now that any overflowed PMCs have been reset.
*/
uncmon_clear_int_locked_l(monid);
/*
* Re-enable all active counters.
*/
uncmon_set_counting_locked_l(monid, uncore_active_ctrs);
uncmon_unlock(mon, intrs_en);
}
static void
uncore_save(void)
{
if (!uncore_active_ctrs) {
return;
}
unsigned int curmonid = uncmon_get_curid();
for (unsigned int monid = 0; monid < uncore_nmonitors(); monid++) {
struct uncore_monitor *mon = &uncore_monitors[monid];
int intrs_en = uncmon_lock(mon);
if (mt_uncore_enabled) {
if (monid != curmonid) {
#if UNCORE_PER_CLUSTER
uncmon_set_counting_locked_r(monid, 0);
#endif /* UNCORE_PER_CLUSTER */
} else {
uncmon_set_counting_locked_l(monid, 0);
}
}
for (unsigned int ctr = 0; ctr < UNCORE_NCTRS; ctr++) {
if (uncore_active_ctrs & (1U << ctr)) {
uncmon_update_locked(monid, curmonid, ctr);
}
}
mon->um_sleeping = true;
uncmon_unlock(mon, intrs_en);
}
}
static void
uncore_restore(void)
{
if (!uncore_active_ctrs) {
return;
}
unsigned int curmonid = uncmon_get_curid();
struct uncore_monitor *mon = &uncore_monitors[curmonid];
int intrs_en = uncmon_lock(mon);
if (!mon->um_sleeping) {
goto out;
}
for (unsigned int ctr = 0; ctr < UNCORE_NCTRS; ctr++) {
if (uncore_active_ctrs & (1U << ctr)) {
uncmon_write_counter_locked_l(curmonid, ctr, mon->um_snaps[ctr]);
}
}
uncmon_program_events_locked_l(curmonid);
uncmon_init_locked_l(curmonid);
mon->um_sleeping = false;
out:
uncmon_unlock(mon, intrs_en);
}
#endif /* HAS_UNCORE_CTRS */
#pragma mark common hooks
void
mt_early_init(void)
{
topology_info = ml_get_topology_info();
}
void
mt_cpu_idle(cpu_data_t *cpu)
{
core_idle(cpu);
}
void
mt_cpu_run(cpu_data_t *cpu)
{
struct mt_cpu *mtc;
assert(cpu != NULL);
assert(ml_get_interrupts_enabled() == FALSE);
mtc = &cpu->cpu_monotonic;
for (int i = 0; i < MT_CORE_NFIXED; i++) {
mt_core_set_snap(i, mtc->mtc_snaps[i]);
}
/* re-enable the counters */
core_init_execution_modes();
core_set_enabled();
}
void
mt_cpu_down(cpu_data_t *cpu)
{
mt_cpu_idle(cpu);
}
void
mt_cpu_up(cpu_data_t *cpu)
{
mt_cpu_run(cpu);
}
void
mt_sleep(void)
{
#if HAS_UNCORE_CTRS
uncore_save();
#endif /* HAS_UNCORE_CTRS */
}
void
mt_wake_per_core(void)
{
#if HAS_UNCORE_CTRS
if (mt_uncore_initted) {
uncore_restore();
}
#endif /* HAS_UNCORE_CTRS */
}
uint64_t
mt_count_pmis(void)
{
uint64_t npmis = 0;
for (unsigned int i = 0; i < topology_info->num_cpus; i++) {
cpu_data_t *cpu = (cpu_data_t *)CpuDataEntries[topology_info->cpus[i].cpu_id].cpu_data_vaddr;
npmis += cpu->cpu_monotonic.mtc_npmis;
}
return npmis;
}
static void
mt_cpu_pmi(cpu_data_t *cpu, uint64_t pmcr0)
{
assert(cpu != NULL);
assert(ml_get_interrupts_enabled() == FALSE);
__builtin_arm_wsr64("PMCR0_EL1", PMCR0_INIT);
/*
* Ensure the CPMU has flushed any increments at this point, so PMSR is up
* to date.
*/
__builtin_arm_isb(ISB_SY);
cpu->cpu_monotonic.mtc_npmis += 1;
cpu->cpu_stat.pmi_cnt_wake += 1;
#if MONOTONIC_DEBUG
if (!PMCR0_PMI(pmcr0)) {
kprintf("monotonic: mt_cpu_pmi but no PMI (PMCR0 = %#llx)\n",
pmcr0);
}
#else /* MONOTONIC_DEBUG */
#pragma unused(pmcr0)
#endif /* !MONOTONIC_DEBUG */
uint64_t pmsr = __builtin_arm_rsr64("PMSR_EL1");
#if MONOTONIC_DEBUG
printf("monotonic: cpu = %d, PMSR = 0x%llx, PMCR0 = 0x%llx\n",
cpu_number(), pmsr, pmcr0);
#endif /* MONOTONIC_DEBUG */
#if MACH_ASSERT
uint64_t handled = 0;
#endif /* MACH_ASSERT */
/*
* monotonic handles any fixed counter PMIs.
*/
for (unsigned int i = 0; i < MT_CORE_NFIXED; i++) {
if ((pmsr & PMSR_OVF(i)) == 0) {
continue;
}
#if MACH_ASSERT
handled |= 1ULL << i;
#endif /* MACH_ASSERT */
uint64_t count = mt_cpu_update_count(cpu, i);
cpu->cpu_monotonic.mtc_counts[i] += count;
mt_core_set_snap(i, mt_core_reset_values[i]);
cpu->cpu_monotonic.mtc_snaps[i] = mt_core_reset_values[i];
if (mt_microstackshots && mt_microstackshot_ctr == i) {
bool user_mode = false;
arm_saved_state_t *state = get_user_regs(current_thread());
if (state) {
user_mode = PSR64_IS_USER(get_saved_state_cpsr(state));
}
KDBG_RELEASE(KDBG_EVENTID(DBG_MONOTONIC, DBG_MT_DEBUG, 1),
mt_microstackshot_ctr, user_mode);
mt_microstackshot_pmi_handler(user_mode, mt_microstackshot_ctx);
} else if (mt_debug) {
KDBG_RELEASE(KDBG_EVENTID(DBG_MONOTONIC, DBG_MT_DEBUG, 2),
i, count);
}
}
/*
* KPC handles the configurable counter PMIs.
*/
for (unsigned int i = MT_CORE_NFIXED; i < CORE_NCTRS; i++) {
if (pmsr & PMSR_OVF(i)) {
#if MACH_ASSERT
handled |= 1ULL << i;
#endif /* MACH_ASSERT */
extern void kpc_pmi_handler(unsigned int ctr);
kpc_pmi_handler(i);
}
}
#if MACH_ASSERT
uint64_t pmsr_after_handling = __builtin_arm_rsr64("PMSR_EL1");
if (pmsr_after_handling != 0) {
unsigned int first_ctr_ovf = __builtin_ffsll(pmsr_after_handling) - 1;
uint64_t count = 0;
const char *extra = "";
if (first_ctr_ovf >= CORE_NCTRS) {
extra = " (invalid counter)";
} else {
count = mt_core_snap(first_ctr_ovf);
}
panic("monotonic: PMI status not cleared on exit from handler, "
"PMSR = 0x%llx HANDLE -> -> 0x%llx, handled 0x%llx, "
"PMCR0 = 0x%llx, PMC%d = 0x%llx%s", pmsr, pmsr_after_handling,
handled, __builtin_arm_rsr64("PMCR0_EL1"), first_ctr_ovf, count, extra);
}
#endif /* MACH_ASSERT */
core_set_enabled();
}
#if CPMU_AIC_PMI
void
mt_cpmu_aic_pmi(cpu_id_t source)
{
struct cpu_data *curcpu = getCpuDatap();
if (source != curcpu->interrupt_nub) {
panic("monotonic: PMI from IOCPU %p delivered to %p", source,
curcpu->interrupt_nub);
}
mt_cpu_pmi(curcpu, __builtin_arm_rsr64("PMCR0_EL1"));
}
#endif /* CPMU_AIC_PMI */
void
mt_fiq(void *cpu, uint64_t pmcr0, uint64_t upmsr)
{
#if CPMU_AIC_PMI
#pragma unused(cpu, pmcr0)
#else /* CPMU_AIC_PMI */
mt_cpu_pmi(cpu, pmcr0);
#endif /* !CPMU_AIC_PMI */
#if HAS_UNCORE_CTRS
uncore_fiq(upmsr);
#else /* HAS_UNCORE_CTRS */
#pragma unused(upmsr)
#endif /* !HAS_UNCORE_CTRS */
}
static uint32_t mt_xc_sync;
static void
mt_microstackshot_start_remote(__unused void *arg)
{
cpu_data_t *cpu = getCpuDatap();
__builtin_arm_wsr64("PMCR0_EL1", PMCR0_INIT);
for (int i = 0; i < MT_CORE_NFIXED; i++) {
uint64_t count = mt_cpu_update_count(cpu, i);
cpu->cpu_monotonic.mtc_counts[i] += count;
mt_core_set_snap(i, mt_core_reset_values[i]);
cpu->cpu_monotonic.mtc_snaps[i] = mt_core_reset_values[i];
}
core_set_enabled();
if (os_atomic_dec(&mt_xc_sync, relaxed) == 0) {
thread_wakeup((event_t)&mt_xc_sync);
}
}
int
mt_microstackshot_start_arch(uint64_t period)
{
uint64_t reset_value = 0;
int ovf = os_sub_overflow(CTR_MAX, period, &reset_value);
if (ovf) {
return ERANGE;
}
mt_core_reset_values[mt_microstackshot_ctr] = reset_value;
cpu_broadcast_xcall(&mt_xc_sync, TRUE, mt_microstackshot_start_remote,
mt_microstackshot_start_remote /* cannot pass NULL */);
return 0;
}
#pragma mark dev nodes
struct mt_device mt_devices[] = {
[0] = {
.mtd_name = "core",
.mtd_init = core_init,
},
#if HAS_UNCORE_CTRS
[1] = {
.mtd_name = "uncore",
.mtd_init = uncore_init,
.mtd_add = uncore_add,
.mtd_reset = uncore_reset,
.mtd_enable = uncore_set_enabled,
.mtd_read = uncore_read,
.mtd_ncounters = UNCORE_NCTRS,
}
#endif /* HAS_UNCORE_CTRS */
};
static_assert(
(sizeof(mt_devices) / sizeof(mt_devices[0])) == MT_NDEVS,
"MT_NDEVS macro should be same as the length of mt_devices");