/* * 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 #include #include #include #include #include #include #include #include #include #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 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(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 ETMRecorder::BuildEventType() { int etm_event_type = GetEtmEventType(); if (etm_event_type == -1) { return nullptr; } return std::make_unique("cs-etm", etm_event_type, 0, "CoreSight ETM instruction tracing", "arm"); } bool ETMRecorder::IsETMDriverAvailable() { return IsDir(ETM_DIR); } expected 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 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 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::max(); for (auto& p : etm_info_) { min_pairs = std::min(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