/*
 * 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 <ranges>
#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/properties.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_;
}

void ETMRecorder::BuildEventTypes(std::set<EventType>& event_types) {
  int etm_event_type = GetEtmEventType();
  if (etm_event_type == -1) {
    return;
  }
  event_types.emplace("cs-etm", etm_event_type, 0, 0, 0, "Coresight ETM instruction tracing",
                      "arm");
  if (CheckSinkSupport() && !etr_sink_configs_.empty()) {
    for (const auto& sink_name : std::views::keys(etr_sink_configs_)) {
      event_types.emplace("cs-etm/@" + sink_name + "/", etm_event_type, 0, 0, 0,
                          "Coresight ETM instruction tracing (via " + sink_name + ")", "arm");
    }
  }
}

bool ETMRecorder::IsETMDriverAvailable() {
  return IsDir(ETM_DIR);
}

expected<bool, std::string> ETMRecorder::CheckEtmSupport(bool need_etr) {
  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");
    }
  }
  bool has_sink = CheckSinkSupport();
  if (!has_sink || (need_etr && etr_sink_configs_.empty())) {
    // Trigger a manual ETR probe under the following two cases:
    // 1. No ETR or TRBE sinks were found.
    // 2. An ETR sink is required but no ETR sinks were found.
    std::string prop_name = "profcollectd.etr.probe";
    bool res = android::base::SetProperty(prop_name, "1");
    if (!res) {
      LOG(ERROR) << "fails to setprop" << prop_name;
    }
    usleep(200000);  // Wait for 200ms.
    has_sink = CheckSinkSupport();
    if (need_etr && etr_sink_configs_.empty()) {
      return unexpected("can't find etr device, which moves etm data to memory");
    }
  }
  if (!has_sink) {
    return unexpected("can't find etr/trbe 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::CheckSinkSupport() {
  has_trbe_sink = false;
  trbe_supported_cpus_.clear();
  etr_sink_configs_.clear();
  for (const auto& name : GetEntriesInDir(ETM_DIR + "sinks")) {
    if (name.find("etr") != -1) {
      uint32_t etr_sink_config = 0;
      if (ReadValueInEtmDir("sinks/" + name, &etr_sink_config)) {
        etr_sink_configs_[name] = etr_sink_config;
      }
    } else if (name.find("trbe") != -1) {
      int cpu;
      if (android::base::ParseInt(&name[4], &cpu)) {
        trbe_supported_cpus_.insert(cpu);
        has_trbe_sink = true;
      }
    }
  }
  return has_trbe_sink || !etr_sink_configs_.empty();
}

static std::string ExtractETRName(const std::string& event_name) {
  // Parse "tmc_etr0" from "cs-etm/@tmc_etr0/".
  auto start = event_name.find("/@");
  if (start != std::string::npos) {
    start += 2;
    auto end = event_name.find("/", start);
    if (end != std::string::npos) {
      return event_name.substr(start, end - start);
    }
  }
  return "";
}

bool ETMRecorder::SetEtmPerfEventAttr(const EventType& event_type, perf_event_attr& attr) {
  CHECK(etm_supported_);
  BuildEtmConfig();
  attr.config = etm_event_config_;
  std::string etr_name = ExtractETRName(event_type.name);
  if (!etr_name.empty()) {
    // User explicitly chooses an ETR.
    auto it = etr_sink_configs_.find(etr_name);
    if (it == etr_sink_configs_.end()) {
      LOG(ERROR) << "Unable to find etr sink for " << event_type.name;
      return false;
    }
    attr.config2 = it->second;
  } else {
    // User doesn't explicitly choose an ETR.
    if (has_trbe_sink) {
      // Prefer using TRBE if it is available.
      attr.config2 = 0;
    } else {
      // Use the first ETR sink.
      CHECK(!etr_sink_configs_.empty());
      attr.config2 = etr_sink_configs_.begin()->second;
    }
  }
  attr.config3 = cc_threshold_config_;
  return true;
}

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;
}

bool ETMRecorder::IsUsingTRBE(const perf_event_attr& attr, int cpu) const {
  if (attr.config2 != 0) {
    return false;
  }
  return trbe_supported_cpus_.find(cpu) != trbe_supported_cpus_.end();
}

}  // namespace simpleperf
