blob: 62e8a485c0f63e2bd1f0e8c8feecda33577125f2 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// See the README.md in this directory for documentation.
#include <assert.h>
#include <cpuid.h>
#include <ddk/debug.h>
#include <ddk/protocol/platform/device.h>
#include <fbl/alloc_checker.h>
#include "perf-mon.h"
namespace perfmon {
// TODO(dje): Having trouble getting this working, so just punt for now.
#define TRY_FREEZE_ON_PMI 0
// Individual bits in the fixed counter enable field.
// See Intel Volume 3, Figure 18-2 "Layout of IA32_FIXED_CTR_CTRL MSR".
#define FIXED_CTR_ENABLE_OS 1
#define FIXED_CTR_ENABLE_USR 2
// This table is sorted at startup.
static EventId misc_event_table_contents[IPM_NUM_MISC_EVENTS] = {
#define DEF_MISC_SKL_EVENT(symbol, event_name, id, offset, size, flags, readable_name, \
description) \
MakeEventId(kGroupMisc, id),
#include <lib/zircon-internal/device/cpu-trace/skylake-misc-events.inc>
};
// Const accessor to give the illusion of the table being const.
static const EventId* misc_event_table = &misc_event_table_contents[0];
enum ArchEvent : uint16_t {
#define DEF_ARCH_EVENT(symbol, event_name, id, ebx_bit, event, umask, flags, readable_name, \
description) \
symbol,
#include <lib/zircon-internal/device/cpu-trace/intel-pm-events.inc>
};
enum ModelEvent : uint16_t {
#define DEF_SKL_EVENT(symbol, event_name, id, event, umask, flags, readable_name, description) \
symbol,
#include <lib/zircon-internal/device/cpu-trace/skylake-pm-events.inc>
};
static const EventDetails kArchEvents[] = {
#define DEF_ARCH_EVENT(symbol, event_name, id, ebx_bit, event, umask, flags, readable_name, \
description) \
{id, event, umask, flags},
#include <lib/zircon-internal/device/cpu-trace/intel-pm-events.inc>
};
static const EventDetails kModelEvents[] = {
#define DEF_SKL_EVENT(symbol, event_name, id, event, umask, flags, readable_name, description) \
{id, event, umask, flags},
#include <lib/zircon-internal/device/cpu-trace/skylake-pm-events.inc>
};
// A table to map event id to index in |kArchEvents|.
// We use the kConstant naming style as once computed it is constant.
static const uint16_t* kArchEventMap;
static size_t kArchEventMapSize;
// A table to map event id to index in |kModelEvents|.
// We use the kConstant naming style as once computed it is constant.
static const uint16_t* kModelEventMap;
static size_t kModelEventMapSize;
// Map a fixed counter event id to its h/w register number.
// Returns IPM_MAX_FIXED_COUNTERS if |id| is unknown.
static unsigned PmuFixedCounterNumber(EventId id) {
enum {
#define DEF_FIXED_EVENT(symbol, event_name, id, regnum, flags, readable_name, description) \
symbol##_NUMBER = regnum,
#include <lib/zircon-internal/device/cpu-trace/intel-pm-events.inc>
};
switch (id) {
case FIXED_INSTRUCTIONS_RETIRED_ID:
return FIXED_INSTRUCTIONS_RETIRED_NUMBER;
case FIXED_UNHALTED_CORE_CYCLES_ID:
return FIXED_UNHALTED_CORE_CYCLES_NUMBER;
case FIXED_UNHALTED_REFERENCE_CYCLES_ID:
return FIXED_UNHALTED_REFERENCE_CYCLES_NUMBER;
default:
return IPM_MAX_FIXED_COUNTERS;
}
}
static void PmuInitMiscEventTable() {
qsort(misc_event_table_contents, countof(misc_event_table_contents),
sizeof(misc_event_table_contents[0]), ComparePerfmonEventId);
}
// Map a misc event id to its ordinal (unique number in range
// 0 ... IPM_NUM_MISC_EVENTS - 1).
// Returns -1 if |id| is unknown.
static int PmuLookupMiscEvent(EventId id) {
auto p =
reinterpret_cast<EventId*>(bsearch(&id, misc_event_table, countof(misc_event_table_contents),
sizeof(id), ComparePerfmonEventId));
if (!p) {
return -1;
}
ptrdiff_t result = p - misc_event_table;
assert(result < IPM_NUM_MISC_EVENTS);
return (int)result;
}
// Initialize the event maps.
// If there's a problem with the database just flag the error but don't crash.
static zx_status_t InitializeEventMaps() {
PmuInitMiscEventTable();
zx_status_t status =
BuildEventMap(kArchEvents, countof(kArchEvents), &kArchEventMap, &kArchEventMapSize);
if (status != ZX_OK) {
return status;
}
status = BuildEventMap(kModelEvents, countof(kModelEvents), &kModelEventMap, &kModelEventMapSize);
if (status != ZX_OK) {
return status;
}
return ZX_OK;
}
// Each arch provides its own |InitOnce()| method.
zx_status_t PerfmonDevice::InitOnce() {
zx_status_t status = InitializeEventMaps();
if (status != ZX_OK) {
return status;
}
return ZX_OK;
}
// Architecture-provided helpers for |PmuStageConfig()|.
static bool LbrSupported(const perfmon::PmuHwProperties& props) { return props.lbr_stack_size > 0; }
void PerfmonDevice::InitializeStagingState(StagingState* ss) {
ss->max_num_fixed = pmu_hw_properties_.common.max_num_fixed_events;
ss->max_num_programmable = pmu_hw_properties_.common.max_num_programmable_events;
ss->max_num_misc = pmu_hw_properties_.common.max_num_misc_events;
ss->max_fixed_value = (pmu_hw_properties_.common.max_fixed_counter_width < 64
? (1ul << pmu_hw_properties_.common.max_fixed_counter_width) - 1
: ~0ul);
ss->max_programmable_value =
(pmu_hw_properties_.common.max_programmable_counter_width < 64
? (1ul << pmu_hw_properties_.common.max_programmable_counter_width) - 1
: ~0ul);
}
zx_status_t PerfmonDevice::StageFixedConfig(const FidlPerfmonConfig* icfg, StagingState* ss,
unsigned input_index, PmuConfig* ocfg) {
const unsigned ii = input_index;
const EventId id = icfg->events[ii].event;
unsigned counter = PmuFixedCounterNumber(id);
EventRate rate = icfg->events[ii].rate;
fidl_perfmon::EventConfigFlags flags = icfg->events[ii].flags;
bool uses_timebase = ocfg->timebase_event != kEventIdNone && rate == 0;
if (counter == IPM_MAX_FIXED_COUNTERS || counter >= countof(ocfg->fixed_events) ||
counter >= ss->max_num_fixed) {
zxlogf(ERROR, "%s: Invalid fixed event [%u]", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
if (ss->have_fixed[counter]) {
zxlogf(ERROR, "%s: Fixed event [%u] already provided", __func__, counter);
return ZX_ERR_INVALID_ARGS;
}
ss->have_fixed[counter] = true;
ocfg->fixed_events[ss->num_fixed] = id;
if (rate == 0) {
ocfg->fixed_initial_value[ss->num_fixed] = 0;
} else {
if (rate > ss->max_fixed_value) {
zxlogf(ERROR, "%s: Rate too large, event [%u]", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
ocfg->fixed_initial_value[ss->num_fixed] = ss->max_fixed_value - rate + 1;
}
// Don't generate PMI's for counters that use another as the timebase.
// We still generate interrupts in "tally mode" in case the counter overflows.
if (!uses_timebase) {
ocfg->fixed_ctrl |= IA32_FIXED_CTR_CTRL_PMI_MASK(counter);
}
unsigned enable = 0;
if (flags & fidl_perfmon::EventConfigFlags::COLLECT_OS) {
enable |= FIXED_CTR_ENABLE_OS;
}
if (flags & fidl_perfmon::EventConfigFlags::COLLECT_USER) {
enable |= FIXED_CTR_ENABLE_USR;
}
ocfg->fixed_ctrl |= enable << IA32_FIXED_CTR_CTRL_EN_SHIFT(counter);
ocfg->global_ctrl |= IA32_PERF_GLOBAL_CTRL_FIXED_EN_MASK(counter);
if (uses_timebase) {
ocfg->fixed_flags[ss->num_fixed] |= kPmuConfigFlagUsesTimebase;
}
if (flags & fidl_perfmon::EventConfigFlags::COLLECT_PC) {
ocfg->fixed_flags[ss->num_fixed] |= kPmuConfigFlagPc;
}
if (flags & fidl_perfmon::EventConfigFlags::COLLECT_LAST_BRANCH) {
if (!LbrSupported(pmu_hw_properties())) {
zxlogf(ERROR, "%s: Last branch not supported, event [%u]", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
ocfg->fixed_flags[ss->num_fixed] |= kPmuConfigFlagLastBranch;
ocfg->debug_ctrl |= IA32_DEBUGCTL_LBR_MASK;
}
++ss->num_fixed;
return ZX_OK;
}
zx_status_t PerfmonDevice::StageProgrammableConfig(const FidlPerfmonConfig* icfg, StagingState* ss,
unsigned input_index, PmuConfig* ocfg) {
const unsigned ii = input_index;
EventId id = icfg->events[ii].event;
unsigned group = GetEventIdGroup(id);
unsigned event = GetEventIdEvent(id);
EventRate rate = icfg->events[ii].rate;
fidl_perfmon::EventConfigFlags flags = icfg->events[ii].flags;
bool uses_timebase = ocfg->timebase_event != kEventIdNone && rate == 0;
// TODO(dje): Verify no duplicates.
if (ss->num_programmable == ss->max_num_programmable) {
zxlogf(ERROR, "%s: Too many programmable counters provided", __func__);
return ZX_ERR_INVALID_ARGS;
}
ocfg->programmable_events[ss->num_programmable] = id;
if (rate == 0) {
ocfg->programmable_initial_value[ss->num_programmable] = 0;
} else {
if (rate > ss->max_programmable_value) {
zxlogf(ERROR, "%s: Rate too large, event [%u]", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
ocfg->programmable_initial_value[ss->num_programmable] = ss->max_programmable_value - rate + 1;
}
const EventDetails* details = nullptr;
switch (group) {
case kGroupArch:
if (event >= kArchEventMapSize) {
zxlogf(ERROR, "%s: Invalid event id, event [%u]", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
details = &kArchEvents[kArchEventMap[event]];
break;
case kGroupModel:
if (event >= kModelEventMapSize) {
zxlogf(ERROR, "%s: Invalid event id, event [%u]", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
details = &kModelEvents[kModelEventMap[event]];
break;
default:
zxlogf(ERROR, "%s: Invalid event id, event [%u]", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
if (details->event == 0 && details->umask == 0) {
zxlogf(ERROR, "%s: Invalid event id, event [%u]", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
uint64_t evtsel = 0;
evtsel |= details->event << IA32_PERFEVTSEL_EVENT_SELECT_SHIFT;
evtsel |= details->umask << IA32_PERFEVTSEL_UMASK_SHIFT;
if (flags & fidl_perfmon::EventConfigFlags::COLLECT_OS) {
evtsel |= IA32_PERFEVTSEL_OS_MASK;
}
if (flags & fidl_perfmon::EventConfigFlags::COLLECT_USER) {
evtsel |= IA32_PERFEVTSEL_USR_MASK;
}
if (details->flags & IPM_REG_FLAG_EDG) {
evtsel |= IA32_PERFEVTSEL_E_MASK;
}
if (details->flags & IPM_REG_FLAG_ANYT) {
evtsel |= IA32_PERFEVTSEL_ANY_MASK;
}
if (details->flags & IPM_REG_FLAG_INV) {
evtsel |= IA32_PERFEVTSEL_INV_MASK;
}
evtsel |= (details->flags & IPM_REG_FLAG_CMSK_MASK) << IA32_PERFEVTSEL_CMASK_SHIFT;
// Don't generate PMI's for counters that use another as the timebase.
// We still generate interrupts in "tally mode" in case the counter overflows.
if (!uses_timebase) {
evtsel |= IA32_PERFEVTSEL_INT_MASK;
}
evtsel |= IA32_PERFEVTSEL_EN_MASK;
ocfg->programmable_hw_events[ss->num_programmable] = evtsel;
ocfg->global_ctrl |= IA32_PERF_GLOBAL_CTRL_PMC_EN_MASK(ss->num_programmable);
if (uses_timebase) {
ocfg->programmable_flags[ss->num_programmable] |= kPmuConfigFlagUsesTimebase;
}
if (flags & fidl_perfmon::EventConfigFlags::COLLECT_PC) {
ocfg->programmable_flags[ss->num_programmable] |= kPmuConfigFlagPc;
}
if (flags & fidl_perfmon::EventConfigFlags::COLLECT_LAST_BRANCH) {
if (!LbrSupported(pmu_hw_properties())) {
zxlogf(ERROR, "%s: Last branch not supported, event [%u]", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
ocfg->programmable_flags[ss->num_programmable] |= kPmuConfigFlagLastBranch;
ocfg->debug_ctrl |= IA32_DEBUGCTL_LBR_MASK;
}
++ss->num_programmable;
return ZX_OK;
}
zx_status_t PerfmonDevice::StageMiscConfig(const FidlPerfmonConfig* icfg, StagingState* ss,
unsigned input_index, PmuConfig* ocfg) {
const unsigned ii = input_index;
EventId id = icfg->events[ii].event;
int event = PmuLookupMiscEvent(id);
EventRate rate = icfg->events[ii].rate;
bool uses_timebase = ocfg->timebase_event != kEventIdNone && rate == 0;
if (event < 0) {
zxlogf(ERROR, "%s: Invalid misc event [%u]", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
if (ss->num_misc == ss->max_num_misc) {
zxlogf(ERROR, "%s: Too many misc counters provided", __func__);
return ZX_ERR_INVALID_ARGS;
}
if (ss->have_misc[event / 64] & (1ul << (event % 64))) {
zxlogf(ERROR, "%s: Misc event [%u] already provided", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
if (rate != 0) {
zxlogf(ERROR, "%s: Misc event [%u] cannot be own timebase", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
ss->have_misc[event / 64] |= 1ul << (event % 64);
ocfg->misc_events[ss->num_misc] = id;
if (uses_timebase) {
ocfg->misc_flags[ss->num_misc] |= kPmuConfigFlagUsesTimebase;
}
++ss->num_misc;
return ZX_OK;
}
zx_status_t PerfmonDevice::VerifyStaging(StagingState* ss, PmuConfig* ocfg) {
PmuPerTraceState* per_trace = per_trace_state_.get();
// Require something to be enabled in order to start tracing.
// This is mostly a sanity check.
if (per_trace->config.global_ctrl == 0) {
zxlogf(ERROR, "%s: Requested config doesn't collect any data", __func__);
return ZX_ERR_INVALID_ARGS;
}
#if TRY_FREEZE_ON_PMI
ocfg->debug_ctrl |= IA32_DEBUGCTL_FREEZE_PERFMON_ON_PMI_MASK;
#endif
return ZX_OK;
}
} // namespace perfmon