blob: 388136434d6f2dc1bd1ca0f8b6ee26075f37d24a [file] [log] [blame]
// Copyright 2018 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 <ddk/debug.h>
#include <ddk/protocol/platform/device.h>
#include "perf-mon.h"
namespace perfmon {
// There's only a few fixed events, so handle them directly.
enum FixedEventId {
#define DEF_FIXED_EVENT(symbol, event_name, id, regnum, flags, readable_name, description) \
symbol ## _ID = PERFMON_MAKE_EVENT_ID(PERFMON_GROUP_FIXED, id),
#include <lib/zircon-internal/device/cpu-trace/arm64-pm-events.inc>
};
// Verify each fixed counter regnum < ARM64_PMU_MAX_FIXED_COUNTERS.
#define DEF_FIXED_EVENT(symbol, event_name, id, regnum, flags, readable_name, description) \
&& (regnum) < ARM64_PMU_MAX_FIXED_COUNTERS
static_assert(1
#include <lib/zircon-internal/device/cpu-trace/arm64-pm-events.inc>
, "");
enum ArchEvent {
#define DEF_ARCH_EVENT(symbol, event_name, id, pmceid_bit, event, flags, readable_name, description) \
symbol,
#include <lib/zircon-internal/device/cpu-trace/arm64-pm-events.inc>
};
static const EventDetails kArchEvents[] = {
#define DEF_ARCH_EVENT(symbol, event_name, id, pmceid_bit, event, flags, readable_name, description) \
{ id, event, flags },
#include <lib/zircon-internal/device/cpu-trace/arm64-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;
// 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() {
zx_status_t status = BuildEventMap(kArchEvents, countof(kArchEvents),
&kArchEventMap, &kArchEventMapSize);
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 = GetHwProperties();
if (status != ZX_OK) {
return status;
}
// KISS and begin with pmu v3.
// Note: This should agree with the kernel driver's check.
if (pmu_hw_properties_.pm_version < 3) {
zxlogf(INFO, "%s: PM version 3 or above is required\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
}
status = InitializeEventMaps();
if (status != ZX_OK) {
return status;
}
zxlogf(TRACE, "ARM64 Performance Monitor configuration for this chipset:\n");
zxlogf(TRACE, "PMU: version: %u\n", pmu_hw_properties_.pm_version);
zxlogf(TRACE, "PMU: num_programmable_events: %u\n",
pmu_hw_properties_.max_num_programmable_events);
zxlogf(TRACE, "PMU: num_fixed_events: %u\n",
pmu_hw_properties_.max_num_fixed_events);
zxlogf(TRACE, "PMU: programmable_counter_width: %u\n",
pmu_hw_properties_.max_programmable_counter_width);
zxlogf(TRACE, "PMU: fixed_counter_width: %u\n",
pmu_hw_properties_.max_fixed_counter_width);
return ZX_OK;
}
// Architecture-provided helpers for |PmuStageConfig()|.
void PerfmonDevice::InitializeStagingState(StagingState* ss) {
ss->max_num_fixed = pmu_hw_properties_.max_num_fixed_events;
ss->max_num_programmable = pmu_hw_properties_.max_num_programmable_events;
ss->max_fixed_value =
(pmu_hw_properties_.max_fixed_counter_width < 64
? (1ul << pmu_hw_properties_.max_fixed_counter_width) - 1
: ~0ul);
ss->max_programmable_value =
(pmu_hw_properties_.max_programmable_counter_width < 64
? (1ul << pmu_hw_properties_.max_programmable_counter_width) - 1
: ~0ul);
}
zx_status_t PerfmonDevice::StageFixedConfig(const perfmon_ioctl_config_t* icfg,
StagingState* ss,
unsigned input_index,
PmuConfig* ocfg) {
const unsigned ii = input_index;
const perfmon_event_id_t id = icfg->events[ii];
bool uses_timebase0 = !!(icfg->flags[ii] & PERFMON_CONFIG_FLAG_TIMEBASE0);
// There's only one fixed counter on ARM64, the cycle counter.
if (id != FIXED_CYCLE_COUNTER_ID) {
zxlogf(ERROR, "%s: Invalid fixed event [%u]\n", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
if (ss->num_fixed > 0) {
zxlogf(ERROR, "%s: Fixed event [%u] already provided\n",
__func__, id);
return ZX_ERR_INVALID_ARGS;
}
ocfg->fixed_events[ss->num_fixed] = id;
if ((uses_timebase0 && input_index != 0) || icfg->rate[ii] == 0) {
ocfg->fixed_initial_value[ss->num_fixed] = 0;
} else {
#if 0 // TODO(ZX-3302): Disable until overflow interrupts are working.
// The cycle counter is 64 bits so there's no need to check
// |icfg->rate[ii]| here.
ZX_DEBUG_ASSERT(ss->max_fixed_value == UINT64_MAX);
ocfg->fixed_initial_value[ss->num_fixed] =
ss->max_fixed_value - icfg->rate[ii] + 1;
#else
zxlogf(ERROR, "%s: data collection rates not supported yet\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
#endif
}
ocfg->fixed_flags[ss->num_fixed] = icfg->flags[ii];
++ss->num_fixed;
return ZX_OK;
}
zx_status_t PerfmonDevice::StageProgrammableConfig(const perfmon_ioctl_config_t* icfg,
StagingState* ss,
unsigned input_index,
PmuConfig* ocfg) {
const unsigned ii = input_index;
perfmon_event_id_t id = icfg->events[ii];
unsigned group = PERFMON_EVENT_ID_GROUP(id);
unsigned event = PERFMON_EVENT_ID_EVENT(id);
bool uses_timebase0 = !!(icfg->flags[ii] & PERFMON_CONFIG_FLAG_TIMEBASE0);
// TODO(dje): Verify no duplicates.
if (ss->num_programmable == ss->max_num_programmable) {
zxlogf(ERROR, "%s: Too many programmable counters provided\n",
__func__);
return ZX_ERR_INVALID_ARGS;
}
ocfg->programmable_events[ss->num_programmable] = id;
if ((uses_timebase0 && input_index != 0) || icfg->rate[ii] == 0) {
ocfg->programmable_initial_value[ss->num_programmable] = 0;
} else {
#if 0 // TODO(ZX-3302): Disable until overflow interrupts are working.
// The cycle counter is 64 bits so there's no need to check
// |icfg->rate[ii]| here.
if (icfg->rate[ii] > ss->max_programmable_value) {
zxlogf(ERROR, "%s: Rate too large, event [%u]\n", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
ocfg->programmable_initial_value[ss->num_programmable] =
ss->max_programmable_value - icfg->rate[ii] + 1;
#else
zxlogf(ERROR, "%s: data collection rates not supported yet\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
#endif
}
const EventDetails* details = NULL;
switch (group) {
case PERFMON_GROUP_ARCH:
if (event >= kArchEventMapSize) {
zxlogf(ERROR, "%s: Invalid event id, event [%u]\n", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
details = &kArchEvents[kArchEventMap[event]];
break;
default:
zxlogf(ERROR, "%s: Invalid event id, event [%u]\n", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
// Arch events have at least ARM64_PMU_REG_FLAG_{ARCH,MICROARCH} set.
if (details->flags == 0) {
zxlogf(ERROR, "%s: Invalid event id, event [%u]\n", __func__, ii);
return ZX_ERR_INVALID_ARGS;
}
ZX_DEBUG_ASSERT((details->flags & (ARM64_PMU_REG_FLAG_ARCH |
ARM64_PMU_REG_FLAG_MICROARCH)) != 0);
ocfg->programmable_hw_events[ss->num_programmable] = details->event;
ocfg->programmable_flags[ss->num_programmable] = icfg->flags[ii];
++ss->num_programmable;
return ZX_OK;
}
zx_status_t PerfmonDevice::StageMiscConfig(const perfmon_ioctl_config_t* icfg,
StagingState* ss,
unsigned input_index,
PmuConfig* ocfg) {
// There are no misc events yet.
zxlogf(ERROR, "%s: Invalid event [%u] (no misc events)\n",
__func__, input_index);
return ZX_ERR_INVALID_ARGS;
}
} // namespace perfmon