#include <mach/mach_types.h>
#include <machine/machine_routines.h>
#include <kern/processor.h>
#include <kern/kalloc.h>
#include <i386/cpuid.h>
#include <i386/proc_reg.h>
#include <i386/mp.h>
#include <i386/lapic.h>
#include <sys/errno.h>
#include <kperf/buffer.h>
#include <kern/kpc.h>
#include <kperf/kperf.h>
#include <kperf/sample.h>
#include <kperf/context.h>
#include <kperf/action.h>
#include <chud/chud_xnu.h>
#define IA32_FIXED_CTR_ENABLE_ALL_CTRS_ALL_RINGS (0x333)
#define IA32_FIXED_CTR_ENABLE_ALL_PMI (0x888)
#define IA32_PERFEVTSEL_PMI (1ull << 20)
#define IA32_PERFEVTSEL_EN (1ull << 22)
#define USE_RDPMC
#define RDPMC_FIXED_COUNTER_SELECTOR (1ULL<<30)
static uint64_t kpc_running_cfg_pmc_mask = 0;
static uint32_t kpc_running_classes = 0;
static uint64_t
IA32_FIXED_CTR_CTRL(void)
{
return rdmsr64( MSR_IA32_PERF_FIXED_CTR_CTRL );
}
static uint64_t
IA32_FIXED_CTRx(uint32_t ctr)
{
#ifdef USE_RDPMC
return rdpmc64(RDPMC_FIXED_COUNTER_SELECTOR | ctr);
#else
return rdmsr64(MSR_IA32_PERF_FIXED_CTR0 + ctr);
#endif
}
#ifdef FIXED_COUNTER_RELOAD
static void
wrIA32_FIXED_CTRx(uint32_t ctr, uint64_t value)
{
return wrmsr64(MSR_IA32_PERF_FIXED_CTR0 + ctr, value);
}
#endif
static uint64_t
IA32_PMCx(uint32_t ctr)
{
#ifdef USE_RDPMC
return rdpmc64(ctr);
#else
return rdmsr64(MSR_IA32_PERFCTR0 + ctr);
#endif
}
static void
wrIA32_PMCx(uint32_t ctr, uint64_t value)
{
return wrmsr64(MSR_IA32_PERFCTR0 + ctr, value);
}
static uint64_t
IA32_PERFEVTSELx(uint32_t ctr)
{
return rdmsr64(MSR_IA32_EVNTSEL0 + ctr);
}
static void
wrIA32_PERFEVTSELx(uint32_t ctr, uint64_t value)
{
wrmsr64(MSR_IA32_EVNTSEL0 + ctr, value);
}
boolean_t
kpc_is_running_fixed(void)
{
return (kpc_running_classes & KPC_CLASS_FIXED_MASK) == KPC_CLASS_FIXED_MASK;
}
boolean_t
kpc_is_running_configurable(uint64_t pmc_mask)
{
assert(kpc_popcount(pmc_mask) <= kpc_configurable_count());
return ((kpc_running_classes & KPC_CLASS_CONFIGURABLE_MASK) == KPC_CLASS_CONFIGURABLE_MASK) &&
((kpc_running_cfg_pmc_mask & pmc_mask) == pmc_mask);
}
uint32_t
kpc_fixed_count(void)
{
i386_cpu_info_t *info = NULL;
info = cpuid_info();
return info->cpuid_arch_perf_leaf.fixed_number;
}
uint32_t
kpc_configurable_count(void)
{
i386_cpu_info_t *info = NULL;
info = cpuid_info();
return info->cpuid_arch_perf_leaf.number;
}
uint32_t
kpc_fixed_config_count(void)
{
return KPC_X86_64_FIXED_CONFIGS;
}
uint32_t
kpc_configurable_config_count(uint64_t pmc_mask)
{
assert(kpc_popcount(pmc_mask) <= kpc_configurable_count());
return kpc_popcount(pmc_mask);
}
uint32_t
kpc_rawpmu_config_count(void)
{
return 0;
}
int
kpc_get_rawpmu_config(__unused kpc_config_t *configv)
{
return 0;
}
static uint8_t
kpc_fixed_width(void)
{
i386_cpu_info_t *info = NULL;
info = cpuid_info();
return info->cpuid_arch_perf_leaf.fixed_width;
}
static uint8_t
kpc_configurable_width(void)
{
i386_cpu_info_t *info = NULL;
info = cpuid_info();
return info->cpuid_arch_perf_leaf.width;
}
uint64_t
kpc_fixed_max(void)
{
return (1ULL << kpc_fixed_width()) - 1;
}
uint64_t
kpc_configurable_max(void)
{
return (1ULL << kpc_configurable_width()) - 1;
}
#ifdef FIXED_COUNTER_SHADOW
static uint64_t
kpc_reload_fixed(int ctr)
{
uint64_t old = IA32_FIXED_CTRx(ctr);
wrIA32_FIXED_CTRx(ctr, FIXED_RELOAD(ctr));
return old;
}
#endif
static uint64_t
kpc_reload_configurable(int ctr)
{
uint64_t cfg = IA32_PERFEVTSELx(ctr);
uint64_t old = IA32_PMCx(ctr);
wrIA32_PERFEVTSELx(ctr, cfg & ~IA32_PERFEVTSEL_EN);
wrIA32_PMCx(ctr, CONFIGURABLE_RELOAD(ctr));
wrIA32_PERFEVTSELx(ctr, cfg);
return old;
}
void kpc_pmi_handler(x86_saved_state_t *state);
static void
set_running_fixed(boolean_t on)
{
uint64_t global = 0, mask = 0, fixed_ctrl = 0;
int i;
boolean_t enabled;
if( on )
fixed_ctrl = IA32_FIXED_CTR_ENABLE_ALL_CTRS_ALL_RINGS | IA32_FIXED_CTR_ENABLE_ALL_PMI;
else
return;
wrmsr64( MSR_IA32_PERF_FIXED_CTR_CTRL, fixed_ctrl );
enabled = ml_set_interrupts_enabled(FALSE);
global = rdmsr64(MSR_IA32_PERF_GLOBAL_CTRL);
for( i = 0; i < (int) kpc_fixed_count(); i++ )
mask |= (1ULL<<(32+i));
if( on )
global |= mask;
else
global &= ~mask;
wrmsr64(MSR_IA32_PERF_GLOBAL_CTRL, global);
ml_set_interrupts_enabled(enabled);
}
static void
set_running_configurable(uint64_t target_mask, uint64_t state_mask)
{
uint32_t cfg_count = kpc_configurable_count();
uint64_t global = 0ULL, cfg = 0ULL, save = 0ULL;
boolean_t enabled;
enabled = ml_set_interrupts_enabled(FALSE);
global = rdmsr64(MSR_IA32_PERF_GLOBAL_CTRL);
for (uint32_t i = 0; i < cfg_count; ++i) {
cfg = IA32_PERFEVTSELx(i);
save = IA32_PMCx(i);
wrIA32_PERFEVTSELx(i, cfg | IA32_PERFEVTSEL_PMI | IA32_PERFEVTSEL_EN);
wrIA32_PMCx(i, save);
}
global &= ~target_mask;
global |= state_mask;
wrmsr64(MSR_IA32_PERF_GLOBAL_CTRL, global);
ml_set_interrupts_enabled(enabled);
}
static void
kpc_set_running_mp_call( void *vstate )
{
struct kpc_running_remote *mp_config = (struct kpc_running_remote*) vstate;
assert(mp_config);
if (kpc_controls_fixed_counters())
set_running_fixed(mp_config->classes & KPC_CLASS_FIXED_MASK);
set_running_configurable(mp_config->cfg_target_mask,
mp_config->cfg_state_mask);
}
int
kpc_get_fixed_config(kpc_config_t *configv)
{
configv[0] = IA32_FIXED_CTR_CTRL();
return 0;
}
static int
kpc_set_fixed_config(kpc_config_t *configv)
{
(void) configv;
return -1;
}
int
kpc_get_fixed_counters(uint64_t *counterv)
{
int i, n = kpc_fixed_count();
#ifdef FIXED_COUNTER_SHADOW
uint64_t status;
for( i = 0; i < n; i++ ) {
counterv[i] = FIXED_SHADOW(ctr) +
(IA32_FIXED_CTRx(i) - FIXED_RELOAD(ctr));
}
status = rdmsr64(MSR_IA32_PERF_GLOBAL_STATUS);
for( i = 0; i < n; i++ ) {
if ((1ull << (i + 32)) & status)
counterv[i] = FIXED_SHADOW(ctr) +
(kpc_fixed_max() - FIXED_RELOAD(ctr) + 1 ) + IA32_FIXED_CTRx(i);
}
#else
for( i = 0; i < n; i++ )
counterv[i] = IA32_FIXED_CTRx(i);
#endif
return 0;
}
int
kpc_get_configurable_config(kpc_config_t *configv, uint64_t pmc_mask)
{
uint32_t cfg_count = kpc_configurable_count();
assert(configv);
for (uint32_t i = 0; i < cfg_count; ++i)
if ((1ULL << i) & pmc_mask)
*configv++ = IA32_PERFEVTSELx(i);
return 0;
}
static int
kpc_set_configurable_config(kpc_config_t *configv, uint64_t pmc_mask)
{
uint32_t cfg_count = kpc_configurable_count();
uint64_t save;
for (uint32_t i = 0; i < cfg_count; i++ ) {
if (((1ULL << i) & pmc_mask) == 0)
continue;
save = IA32_PMCx(i);
wrIA32_PERFEVTSELx(i, *configv & 0xffc7ffffull);
wrIA32_PMCx(i, save);
configv++;
}
return 0;
}
int
kpc_get_configurable_counters(uint64_t *counterv, uint64_t pmc_mask)
{
uint32_t cfg_count = kpc_configurable_count();
uint64_t status, *it_counterv = counterv;
for (uint32_t i = 0; i < cfg_count; ++i) {
if ((1ULL << i) & pmc_mask) {
*it_counterv++ = CONFIGURABLE_SHADOW(i) +
(IA32_PMCx(i) - CONFIGURABLE_RELOAD(i));
}
}
status = rdmsr64(MSR_IA32_PERF_GLOBAL_STATUS);
it_counterv = counterv;
for (uint32_t i = 0; i < cfg_count; ++i) {
if (((1ULL << i) & pmc_mask) &&
((1ULL << i) & status))
{
*it_counterv++ = CONFIGURABLE_SHADOW(i) +
(kpc_configurable_max() - CONFIGURABLE_RELOAD(i)) + IA32_PMCx(i);
}
}
return 0;
}
static void
kpc_get_curcpu_counters_mp_call(void *args)
{
struct kpc_get_counters_remote *handler = args;
int offset=0, r=0;
assert(handler);
assert(handler->buf);
offset = cpu_number() * handler->buf_stride;
r = kpc_get_curcpu_counters(handler->classes, NULL, &handler->buf[offset]);
hw_atomic_add(&(handler->nb_counters), r);
}
int
kpc_get_all_cpus_counters(uint32_t classes, int *curcpu, uint64_t *buf)
{
int enabled = 0;
struct kpc_get_counters_remote hdl = {
.classes = classes, .nb_counters = 0,
.buf_stride = kpc_get_counter_count(classes), .buf = buf
};
assert(buf);
enabled = ml_set_interrupts_enabled(FALSE);
if (curcpu)
*curcpu = current_processor()->cpu_id;
mp_cpus_call(CPUMASK_ALL, ASYNC, kpc_get_curcpu_counters_mp_call, &hdl);
ml_set_interrupts_enabled(enabled);
return hdl.nb_counters;
}
static void
kpc_set_config_mp_call(void *vmp_config)
{
struct kpc_config_remote *mp_config = vmp_config;
kpc_config_t *new_config = NULL;
uint32_t classes = 0, count = 0;
boolean_t enabled;
assert(mp_config);
assert(mp_config->configv);
classes = mp_config->classes;
new_config = mp_config->configv;
enabled = ml_set_interrupts_enabled(FALSE);
if (classes & KPC_CLASS_FIXED_MASK)
{
kpc_set_fixed_config(&new_config[count]);
count += kpc_get_config_count(KPC_CLASS_FIXED_MASK);
}
if (classes & KPC_CLASS_CONFIGURABLE_MASK) {
kpc_set_configurable_config(&new_config[count], mp_config->pmc_mask);
count += kpc_popcount(mp_config->pmc_mask);
}
ml_set_interrupts_enabled(enabled);
}
static void
kpc_set_reload_mp_call(void *vmp_config)
{
struct kpc_config_remote *mp_config = vmp_config;
uint64_t *new_period = NULL, max = kpc_configurable_max();
uint32_t classes = 0, count = 0;
boolean_t enabled;
assert(mp_config);
assert(mp_config->configv);
classes = mp_config->classes;
new_period = mp_config->configv;
enabled = ml_set_interrupts_enabled(FALSE);
if (classes & KPC_CLASS_CONFIGURABLE_MASK) {
uint64_t all_cfg_mask = (1ULL << kpc_configurable_count()) - 1;
kpc_get_configurable_counters(&CONFIGURABLE_SHADOW(0), all_cfg_mask);
count = kpc_configurable_count();
for (uint32_t i = 0; i < count; ++i) {
if (((1ULL << i) & mp_config->pmc_mask) == 0)
continue;
if (*new_period == 0)
*new_period = kpc_configurable_max();
CONFIGURABLE_RELOAD(i) = max - *new_period;
kpc_reload_configurable(i);
wrmsr64(MSR_IA32_PERF_GLOBAL_OVF_CTRL, 1ull << i);
new_period++;
}
}
ml_set_interrupts_enabled(enabled);
}
int
kpc_set_period_arch( struct kpc_config_remote *mp_config )
{
mp_cpus_call( CPUMASK_ALL, ASYNC, kpc_set_reload_mp_call, mp_config );
return 0;
}
void
kpc_arch_init(void)
{
}
uint32_t
kpc_get_classes(void)
{
return KPC_CLASS_FIXED_MASK | KPC_CLASS_CONFIGURABLE_MASK;
}
int
kpc_set_running_arch(struct kpc_running_remote *mp_config)
{
assert(mp_config);
lapic_set_pmi_func((i386_intr_func_t)kpc_pmi_handler);
mp_cpus_call(CPUMASK_ALL, ASYNC, kpc_set_running_mp_call, mp_config);
kpc_running_cfg_pmc_mask = mp_config->cfg_state_mask;
kpc_running_classes = mp_config->classes;
return 0;
}
int
kpc_set_config_arch(struct kpc_config_remote *mp_config)
{
mp_cpus_call( CPUMASK_ALL, ASYNC, kpc_set_config_mp_call, mp_config );
return 0;
}
void kpc_pmi_handler(__unused x86_saved_state_t *state)
{
uint64_t status, extra;
uint32_t ctr;
int enabled;
enabled = ml_set_interrupts_enabled(FALSE);
status = rdmsr64(MSR_IA32_PERF_GLOBAL_STATUS);
#ifdef FIXED_COUNTER_SHADOW
for (ctr = 0; ctr < kpc_fixed_count(); ctr++) {
if ((1ULL << (ctr + 32)) & status) {
extra = kpc_reload_fixed(ctr);
FIXED_SHADOW(ctr)
+= (kpc_fixed_max() - FIXED_RELOAD(ctr) + 1 ) + extra;
BUF_INFO(PERF_KPC_FCOUNTER, ctr, FIXED_SHADOW(ctr), extra, FIXED_ACTIONID(ctr));
if (FIXED_ACTIONID(ctr))
kpc_sample_kperf(FIXED_ACTIONID(ctr));
}
}
#endif
for (ctr = 0; ctr < kpc_configurable_count(); ctr++) {
if ((1ULL << ctr) & status) {
extra = kpc_reload_configurable(ctr);
CONFIGURABLE_SHADOW(ctr)
+= kpc_configurable_max() - CONFIGURABLE_RELOAD(ctr) + extra;
wrmsr64(MSR_IA32_PERF_GLOBAL_OVF_CTRL, 1ull << ctr);
BUF_INFO(PERF_KPC_COUNTER, ctr, CONFIGURABLE_SHADOW(ctr), extra, CONFIGURABLE_ACTIONID(ctr));
if (CONFIGURABLE_ACTIONID(ctr))
kpc_sample_kperf(CONFIGURABLE_ACTIONID(ctr));
}
}
ml_set_interrupts_enabled(enabled);
}
int
kpc_set_sw_inc( uint32_t mask __unused )
{
return ENOTSUP;
}
int
kpc_get_pmu_version(void)
{
i386_cpu_info_t *info = cpuid_info();
uint8_t version_id = info->cpuid_arch_perf_leaf.version;
if (version_id == 3) {
return KPC_PMU_INTEL_V3;
} else if (version_id == 2) {
return KPC_PMU_INTEL_V2;
}
return KPC_PMU_ERROR;
}