blob: a722df5c3cc4ceecbfbdaa750d1cb3f97362cc25 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ETMRecorder.h"
#include <stdio.h>
#include <sys/sysinfo.h>
#include <limits>
#include <memory>
#include <string>
#include <android-base/expected.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include "ETMConstants.h"
#include "environment.h"
#include "utils.h"
namespace simpleperf {
using android::base::expected;
using android::base::unexpected;
static const std::string ETM_DIR = "/sys/bus/event_source/devices/cs_etm/";
// from coresight_get_trace_id(int cpu) in include/linux/coresight-pmu.h
static int GetTraceId(int cpu) {
return 0x10 + cpu * 2;
}
template <typename T>
static bool ReadValueInEtmDir(const std::string& file, T* value, bool report_error = true,
const std::string& prefix = "") {
std::string s;
uint64_t v;
if (!android::base::ReadFileToString(ETM_DIR + file, &s) ||
!android::base::StartsWith(s, prefix) ||
!android::base::ParseUint(&android::base::Trim(s)[prefix.size()], &v)) {
if (report_error) {
LOG(ERROR) << "failed to read " << ETM_DIR << file;
}
return false;
}
*value = static_cast<T>(v);
return true;
}
static uint32_t GetBits(uint32_t value, int start, int end) {
return (value >> start) & ((1U << (end - start + 1)) - 1);
}
int ETMPerCpu::GetMajorVersion() const {
return GetBits(trcidr1, 8, 11);
}
bool ETMPerCpu::IsContextIDSupported() const {
return GetBits(trcidr2, 5, 9) >= 4;
}
bool ETMPerCpu::IsTimestampSupported() const {
return GetBits(trcidr0, 24, 28) > 0;
}
bool ETMPerCpu::IsCycAccSupported() const {
return GetBits(trcidr0, 7, 7);
}
bool ETMPerCpu::IsEnabled() const {
return GetBits(trcauthstatus, 0, 3) == 0xc;
}
ETMRecorder& ETMRecorder::GetInstance() {
static ETMRecorder etm;
return etm;
}
int ETMRecorder::GetEtmEventType() {
if (event_type_ == 0) {
if (!IsDir(ETM_DIR) || !ReadValueInEtmDir("type", &event_type_, false)) {
event_type_ = -1;
}
}
return event_type_;
}
std::unique_ptr<EventType> ETMRecorder::BuildEventType() {
int etm_event_type = GetEtmEventType();
if (etm_event_type == -1) {
return nullptr;
}
return std::make_unique<EventType>("cs-etm", etm_event_type, 0,
"CoreSight ETM instruction tracing", "arm");
}
bool ETMRecorder::IsETMDriverAvailable() {
return IsDir(ETM_DIR);
}
expected<bool, std::string> ETMRecorder::CheckEtmSupport() {
if (GetEtmEventType() == -1) {
return unexpected("etm event type isn't supported on device");
}
if (!ReadEtmInfo()) {
return unexpected("etm devices are not available");
}
for (const auto& p : etm_info_) {
if (p.second.GetMajorVersion() < 4) {
return unexpected("etm device version is less than 4.0");
}
if (!p.second.IsContextIDSupported()) {
return unexpected("etm device doesn't support contextID");
}
if (!p.second.IsEnabled()) {
return unexpected("etm device isn't enabled by the bootloader");
}
}
if (!FindSinkConfig()) {
return unexpected("can't find etr device, which moves etm data to memory");
}
etm_supported_ = true;
return true;
}
bool ETMRecorder::ReadEtmInfo() {
int contextid_value;
use_contextid2_ = ReadValueInEtmDir("/format/contextid", &contextid_value, false, "config:") &&
contextid_value == ETM_OPT_CTXTID2;
std::vector<int> online_cpus = GetOnlineCpus();
for (const auto& name : GetEntriesInDir(ETM_DIR)) {
int cpu;
if (sscanf(name.c_str(), "cpu%d", &cpu) == 1) {
// We can't read ETM registers for offline cpus. So skip them.
if (std::find(online_cpus.begin(), online_cpus.end(), cpu) == online_cpus.end()) {
continue;
}
ETMPerCpu& cpu_info = etm_info_[cpu];
bool success = ReadValueInEtmDir(name + "/trcidr/trcidr0", &cpu_info.trcidr0) &&
ReadValueInEtmDir(name + "/trcidr/trcidr1", &cpu_info.trcidr1) &&
ReadValueInEtmDir(name + "/trcidr/trcidr2", &cpu_info.trcidr2) &&
ReadValueInEtmDir(name + "/trcidr/trcidr4", &cpu_info.trcidr4) &&
ReadValueInEtmDir(name + "/trcidr/trcidr8", &cpu_info.trcidr8) &&
ReadValueInEtmDir(name + "/mgmt/trcauthstatus", &cpu_info.trcauthstatus);
if (!ReadValueInEtmDir(name + "/mgmt/trcdevarch", &cpu_info.trcdevarch, false)) {
cpu_info.trcdevarch = 0;
}
if (!success) {
return false;
}
}
}
return (etm_info_.size() == online_cpus.size());
}
bool ETMRecorder::FindSinkConfig() {
bool has_etr = false;
bool has_trbe = false;
for (const auto& name : GetEntriesInDir(ETM_DIR + "sinks")) {
if (!has_etr && name.find("etr") != -1) {
if (ReadValueInEtmDir("sinks/" + name, &sink_config_)) {
has_etr = true;
}
}
if (name.find("trbe") != -1) {
has_trbe = true;
break;
}
}
if (has_trbe) {
// When TRBE is present, let the driver choose the most suitable
// configuration.
sink_config_ = 0;
}
return has_trbe || has_etr;
}
void ETMRecorder::SetEtmPerfEventAttr(perf_event_attr* attr) {
CHECK(etm_supported_);
BuildEtmConfig();
attr->config = etm_event_config_;
attr->config2 = sink_config_;
attr->config3 = cc_threshold_config_;
}
void ETMRecorder::BuildEtmConfig() {
if (etm_event_config_ == 0) {
if (use_contextid2_) {
etm_event_config_ |= 1ULL << ETM_OPT_CTXTID2;
etm_config_reg_ |= 1U << ETM4_CFG_BIT_VMID;
etm_config_reg_ |= 1U << ETM4_CFG_BIT_VMID_OPT;
} else {
etm_event_config_ |= 1ULL << ETM_OPT_CTXTID;
etm_config_reg_ |= 1U << ETM4_CFG_BIT_CTXTID;
}
if (record_timestamp_) {
bool ts_supported = true;
for (auto& p : etm_info_) {
ts_supported &= p.second.IsTimestampSupported();
}
if (ts_supported) {
etm_event_config_ |= 1ULL << ETM_OPT_TS;
etm_config_reg_ |= 1U << ETM4_CFG_BIT_TS;
}
}
if (record_cycles_) {
bool cycles_supported = true;
for (auto& p : etm_info_) {
cycles_supported &= p.second.IsCycAccSupported();
}
if (cycles_supported) {
etm_event_config_ |= 1ULL << ETM_OPT_CYCACC;
etm_config_reg_ |= 1U << ETM4_CFG_BIT_CCI;
if (cycle_threshold_) {
cc_threshold_config_ |= cycle_threshold_;
}
}
}
}
}
AuxTraceInfoRecord ETMRecorder::CreateAuxTraceInfoRecord() {
AuxTraceInfoRecord::DataType data;
memset(&data, 0, sizeof(data));
data.aux_type = AuxTraceInfoRecord::AUX_TYPE_ETM;
data.version = 1;
data.nr_cpu = etm_info_.size();
data.pmu_type = GetEtmEventType();
std::vector<AuxTraceInfoRecord::ETEInfo> ete(etm_info_.size());
size_t pos = 0;
for (auto& p : etm_info_) {
auto& e = ete[pos++];
if (p.second.trcdevarch == 0) {
e.magic = AuxTraceInfoRecord::MAGIC_ETM4;
e.nrtrcparams = sizeof(AuxTraceInfoRecord::ETM4Info) / sizeof(uint64_t) - 3;
} else {
e.magic = AuxTraceInfoRecord::MAGIC_ETE;
e.nrtrcparams = sizeof(AuxTraceInfoRecord::ETEInfo) / sizeof(uint64_t) - 3;
}
e.cpu = p.first;
e.trcconfigr = etm_config_reg_;
e.trctraceidr = GetTraceId(p.first);
e.trcidr0 = p.second.trcidr0;
e.trcidr1 = p.second.trcidr1;
e.trcidr2 = p.second.trcidr2;
e.trcidr8 = p.second.trcidr8;
e.trcauthstatus = p.second.trcauthstatus;
e.trcdevarch = p.second.trcdevarch;
}
return AuxTraceInfoRecord(data, ete);
}
size_t ETMRecorder::GetAddrFilterPairs() {
CHECK(etm_supported_);
size_t min_pairs = std::numeric_limits<size_t>::max();
for (auto& p : etm_info_) {
min_pairs = std::min<size_t>(min_pairs, GetBits(p.second.trcidr4, 0, 3));
}
if (min_pairs > 0) {
--min_pairs; // One pair is used by the kernel to set default addr filter.
}
return min_pairs;
}
void ETMRecorder::SetRecordTimestamp(bool record) {
record_timestamp_ = record;
}
void ETMRecorder::SetRecordCycles(bool record) {
record_cycles_ = record;
}
void ETMRecorder::SetCycleThreshold(size_t threshold) {
cycle_threshold_ = threshold;
}
} // namespace simpleperf