text: // 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.
  
  #include <memory>
  #include <string>
  
  #include "msd_intel_device.h"
  #include "registers.h"
  
  class InstructionDecoder {
   public:
    enum Id {
      NOOP = 0x0,
      MI_BATCH_BUFFER_END = 0x0500,
      LOAD_REGISTER_IMM = 0x1100,
      _3DSTATE_CLEAR_PARAMS = 0x7804,
      _3DSTATE_DEPTH_BUFFER = 0x7805,
      _3DSTATE_STENCIL_BUFFER = 0x7806,
      _3DSTATE_HIER_DEPTH_BUFFER = 0x7807,
      _3DSTATE_VERTEX_BUFFERS = 0x7808,
      _3DSTATE_VERTEX_ELEMENTS = 0x7809,
      _3DSTATE_MULTISAMPLE = 0x780d,
      _3DSTATE_INDEX_BUFFER = 0x780a,
      _3DSTATE_VF = 0x780c,
      _3DSTATE_SCISSOR_STATE_POINTERS = 0x780f,
      _3DSTATE_VS = 0x7810,
      _3DSTATE_GS = 0x7811,
      _3DSTATE_CLIP = 0x7812,
      _3DSTATE_SF = 0x7813,
      _3DSTATE_WM = 0x7814,
      _3DSTATE_CONSTANT_VS = 0x7815,
      _3DSTATE_CONSTANT_GS = 0x7816,
      _3DSTATE_CONSTANT_PS = 0x7817,
      _3DSTATE_SAMPLE_MASK = 0x7818,
      _3DSTATE_CONSTANT_HS = 0x7819,
      _3DSTATE_CONSTANT_DS = 0x781a,
      _3DSTATE_HS = 0x781b,
      _3DSTATE_TE = 0x781c,
      _3DSTATE_DS = 0x781d,
      _3DSTATE_STREAMOUT = 0x781e,
      _3DSTATE_SBE = 0x781f,
      _3DSTATE_PS = 0x7820,
      _3DSTATE_VIEWPORT_STATE_POINTERS_SF_CLIP = 0x7821,
      _3DSTATE_VIEWPORT_STATE_POINTERS_CC = 0x7823,
      _3DSTATE_BINDING_TABLE_POINTERS_VS = 0x7826,
      _3DSTATE_BINDING_TABLE_POINTERS_HS = 0x7827,
      _3DSTATE_BINDING_TABLE_POINTERS_DS = 0x7828,
      _3DSTATE_BINDING_TABLE_POINTERS_GS = 0x7829,
      _3DSTATE_BINDING_TABLE_POINTERS_PS = 0x782a,
      _3DSTATE_SAMPLER_STATE_POINTERS_PS = 0x782f,
      _3DSTATE_CC_STATE_POINTERS = 0x780e,
      _3DSTATE_BLEND_STATE_POINTERS = 0x7824,
      _3DSTATE_URB_VS = 0x7830,
      _3DSTATE_URB_HS = 0x7831,
      _3DSTATE_URB_DS = 0x7832,
      _3DSTATE_URB_GS = 0x7833,
      _3DSTATE_VF_INSTANCING = 0x7849,
      _3DSTATE_VF_SGVS = 0x784a,
      _3DSTATE_VF_TOPOLOGY = 0x784b,
      _3DSTATE_PS_BLEND = 0x784d,
      _3DSTATE_WM_DEPTH_STENCIL = 0x784e,
      _3DSTATE_PS_EXTRA = 0x784f,
      _3DSTATE_RASTER = 0x7850,
      _3DSTATE_SBE_SWIZ = 0x7851,
      _3DSTATE_WM_HZ_OP = 0x7852,
      _3DSTATE_PUSH_CONSTANT_ALLOC_VS = 0x7912,
      _3DSTATE_PUSH_CONSTANT_ALLOC_HS = 0x7913,
      _3DSTATE_PUSH_CONSTANT_ALLOC_DS = 0x7914,
      _3DSTATE_PUSH_CONSTANT_ALLOC_GS = 0x7915,
      _3DSTATE_PUSH_CONSTANT_ALLOC_PS = 0x7916,
      PIPE_CONTROL = 0x7a00,
      _3DPRIMITIVE = 0x7b00,
      STATE_BASE_ADDRESS = 0x6101,
      PIPELINE_SELECT = 0x6904,
    };
  
    static const char* name(Id id) {
      switch (id) {
        case _3DSTATE_VERTEX_BUFFERS:
          return "3DSTATE_VERTEX_BUFFERS";
        case _3DSTATE_VERTEX_ELEMENTS:
          return "3DSTATE_VERTEX_ELEMENTS";
        case LOAD_REGISTER_IMM:
          return "LOAD_REGISTER_IMM";
        case PIPE_CONTROL:
          return "PIPE_CONTROL";
        case PIPELINE_SELECT:
          return "PIPELINE_SELECT";
        case STATE_BASE_ADDRESS:
          return "STATE_BASE_ADDRESS";
        case _3DSTATE_VF_SGVS:
          return "3DSTATE_VF_SGVS";
        case _3DSTATE_VF_INSTANCING:
          return "3DSTATE_VF_INSTANCING";
        case _3DSTATE_VF_TOPOLOGY:
          return "3DSTATE_VF_TOPOLOGY";
        case _3DSTATE_URB_VS:
          return "3DSTATE_URB_VS";
        case _3DSTATE_URB_HS:
          return "3DSTATE_URB_HS";
        case _3DSTATE_URB_DS:
          return "3DSTATE_URB_DS";
        case _3DSTATE_URB_GS:
          return "3DSTATE_URB_GS";
        case _3DSTATE_BLEND_STATE_POINTERS:
          return "3DSTATE_BLEND_STATE_POINTERS";
        case _3DSTATE_PS_BLEND:
          return "3DSTATE_PS_BLEND";
        case _3DSTATE_CC_STATE_POINTERS:
          return "3DSTATE_CC_STATE_POINTERS";
        case _3DSTATE_WM_DEPTH_STENCIL:
          return "3DSTATE_WM_DEPTH_STENCIL";
        case _3DSTATE_CONSTANT_VS:
          return "3DSTATE_CONSTANT_VS";
        case _3DSTATE_CONSTANT_HS:
          return "3DSTATE_CONSTANT_HS";
        case _3DSTATE_CONSTANT_DS:
          return "3DSTATE_CONSTANT_DS";
        case _3DSTATE_CONSTANT_GS:
          return "3DSTATE_CONSTANT_GS";
        case _3DSTATE_CONSTANT_PS:
          return "3DSTATE_CONSTANT_PS";
        case _3DSTATE_BINDING_TABLE_POINTERS_VS:
          return "3DSTATE_BINDING_TABLE_POINTERS_VS";
        case _3DSTATE_BINDING_TABLE_POINTERS_HS:
          return "3DSTATE_BINDING_TABLE_POINTERS_HS";
        case _3DSTATE_BINDING_TABLE_POINTERS_DS:
          return "3DSTATE_BINDING_TABLE_POINTERS_DS";
        case _3DSTATE_BINDING_TABLE_POINTERS_GS:
          return "3DSTATE_BINDING_TABLE_POINTERS_GS";
        case _3DSTATE_BINDING_TABLE_POINTERS_PS:
          return "3DSTATE_BINDING_TABLE_POINTERS_PS";
        case _3DSTATE_SAMPLER_STATE_POINTERS_PS:
          return "3DSTATE_SAMPLER_STATE_POINTERS_PS";
        case _3DSTATE_MULTISAMPLE:
          return "3DSTATE_MULTISAMPLE";
        case _3DSTATE_SAMPLE_MASK:
          return "3DSTATE_SAMPLE_MASK";
        case _3DSTATE_VS:
          return "3DSTATE_VS";
        case _3DSTATE_HS:
          return "3DSTATE_HS";
        case _3DSTATE_TE:
          return "3DSTATE_TE";
        case _3DSTATE_DS:
          return "3DSTATE_DS";
        case _3DSTATE_STREAMOUT:
          return "3DSTATE_STREAMOUT";
        case _3DSTATE_GS:
          return "3DSTATE_GS";
        case _3DSTATE_CLIP:
          return "3DSTATE_CLIP";
        case _3DSTATE_SF:
          return "3DSTATE_SF";
        case _3DSTATE_RASTER:
          return "3DSTATE_RASTER";
        case _3DSTATE_SBE:
          return "3DSTATE_SBE";
        case _3DSTATE_WM:
          return "3DSTATE_WM";
        case _3DSTATE_PS:
          return "3DSTATE_PS";
        case _3DSTATE_PS_EXTRA:
          return "3DSTATE_PS_EXTRA";
        case _3DSTATE_VIEWPORT_STATE_POINTERS_CC:
          return "3DSTATE_VIEWPORT_STATE_POINTERS_CC";
        case _3DSTATE_DEPTH_BUFFER:
          return "3DSTATE_DEPTH_BUFFER";
        case _3DSTATE_HIER_DEPTH_BUFFER:
          return "3DSTATE_HIER_DEPTH_BUFFER";
        case _3DSTATE_STENCIL_BUFFER:
          return "3DSTATE_STENCIL_BUFFER";
        case _3DSTATE_CLEAR_PARAMS:
          return "3DSTATE_CLEAR_PARAMS";
        case _3DPRIMITIVE:
          return "3DPRIMITIVE";
        case _3DSTATE_INDEX_BUFFER:
          return "3DSTATE_INDEX_BUFFER";
        case _3DSTATE_SBE_SWIZ:
          return "3DSTATE_SBE_SWIZ";
        case _3DSTATE_PUSH_CONSTANT_ALLOC_VS:
          return "3DSTATE_PUSH_CONSTANT_ALLOC_VS";
        case _3DSTATE_PUSH_CONSTANT_ALLOC_HS:
          return "3DSTATE_PUSH_CONSTANT_ALLOC_HS";
        case _3DSTATE_PUSH_CONSTANT_ALLOC_DS:
          return "3DSTATE_PUSH_CONSTANT_ALLOC_DS";
        case _3DSTATE_PUSH_CONSTANT_ALLOC_GS:
          return "3DSTATE_PUSH_CONSTANT_ALLOC_GS";
        case _3DSTATE_PUSH_CONSTANT_ALLOC_PS:
          return "3DSTATE_PUSH_CONSTANT_ALLOC_PS";
        case _3DSTATE_WM_HZ_OP:
          return "3DSTATE_WM_HZ_OP";
        case _3DSTATE_VIEWPORT_STATE_POINTERS_SF_CLIP:
          return "3DSTATE_VIEWPORT_STATE_POINTERS_SF_CLIP";
        case _3DSTATE_SCISSOR_STATE_POINTERS:
          return "3DSTATE_SCISSOR_STATE_POINTERS";
        case _3DSTATE_VF:
          return "3DSTATE_VF";
        case MI_BATCH_BUFFER_END:
          return "MI_BATCH_BUFFER_END";
        case NOOP:
          return "NOOP";
      }
      return "UNKNOWN";
    }
  
    static bool Decode(uint32_t dword, Id* id_out, uint32_t* dword_count_out) {
      if (dword == 0) {
        *id_out = NOOP;
        *dword_count_out = 1;
        return true;
      }
  
      uint16_t id = dword >> 16;
      switch (id) {
        case PIPELINE_SELECT:
        case MI_BATCH_BUFFER_END:
          *dword_count_out = 1;
          break;
        case LOAD_REGISTER_IMM:
          *dword_count_out = 3;
          break;
        case _3DSTATE_BLEND_STATE_POINTERS:
        case _3DSTATE_CC_STATE_POINTERS:
        case _3DSTATE_VIEWPORT_STATE_POINTERS_CC:
          *dword_count_out = 2;
          break;
        case _3DSTATE_VF:
        case _3DSTATE_SCISSOR_STATE_POINTERS:
        case _3DSTATE_VIEWPORT_STATE_POINTERS_SF_CLIP:
        case _3DSTATE_PUSH_CONSTANT_ALLOC_PS:
        case _3DSTATE_PUSH_CONSTANT_ALLOC_GS:
        case _3DSTATE_PUSH_CONSTANT_ALLOC_DS:
        case _3DSTATE_PUSH_CONSTANT_ALLOC_HS:
        case _3DSTATE_PUSH_CONSTANT_ALLOC_VS:
        case _3DSTATE_SBE_SWIZ:
        case _3DSTATE_INDEX_BUFFER:
        case _3DPRIMITIVE:
        case _3DSTATE_CLEAR_PARAMS:
        case _3DSTATE_STENCIL_BUFFER:
        case _3DSTATE_HIER_DEPTH_BUFFER:
        case _3DSTATE_DEPTH_BUFFER:
        case _3DSTATE_PS_EXTRA:
        case _3DSTATE_PS:
        case _3DSTATE_WM:
        case _3DSTATE_SBE:
        case _3DSTATE_RASTER:
        case _3DSTATE_SF:
        case _3DSTATE_CLIP:
        case _3DSTATE_GS:
        case _3DSTATE_STREAMOUT:
        case _3DSTATE_DS:
        case _3DSTATE_TE:
        case _3DSTATE_VS:
        case _3DSTATE_HS:
        case _3DSTATE_SAMPLE_MASK:
        case _3DSTATE_MULTISAMPLE:
        case _3DSTATE_SAMPLER_STATE_POINTERS_PS:
        case _3DSTATE_BINDING_TABLE_POINTERS_PS:
        case _3DSTATE_BINDING_TABLE_POINTERS_GS:
        case _3DSTATE_BINDING_TABLE_POINTERS_DS:
        case _3DSTATE_BINDING_TABLE_POINTERS_HS:
        case _3DSTATE_BINDING_TABLE_POINTERS_VS:
        case _3DSTATE_CONSTANT_PS:
        case _3DSTATE_CONSTANT_GS:
        case _3DSTATE_CONSTANT_DS:
        case _3DSTATE_CONSTANT_HS:
        case _3DSTATE_CONSTANT_VS:
        case _3DSTATE_WM_DEPTH_STENCIL:
        case _3DSTATE_PS_BLEND:
        case _3DSTATE_URB_GS:
        case _3DSTATE_URB_DS:
        case _3DSTATE_URB_HS:
        case _3DSTATE_URB_VS:
        case _3DSTATE_VF_TOPOLOGY:
        case _3DSTATE_VF_INSTANCING:
        case _3DSTATE_VF_SGVS:
        case _3DSTATE_VERTEX_BUFFERS:
        case _3DSTATE_VERTEX_ELEMENTS:
        case _3DSTATE_WM_HZ_OP:
        case PIPE_CONTROL:
        case STATE_BASE_ADDRESS:
          *dword_count_out = (dword & 0xFF) + 2;
          break;
      }
      *id_out = static_cast<Id>(id);
      return true;
    }
  };
  
  void MsdIntelDevice::Dump(DumpState* dump_out) {
    dump_out->render_cs.sequence_number =
        global_context_->hardware_status_page(render_engine_cs_->id())->read_sequence_number();
    dump_out->render_cs.active_head_pointer = render_engine_cs_->GetActiveHeadPointer();
    dump_out->render_cs.inflight_batches = render_engine_cs_->GetInflightBatches();
  
    DumpFault(dump_out, registers::AllEngineFault::read(register_io_.get()));
  
    dump_out->fault_gpu_address = kInvalidGpuAddr;
    dump_out->global = false;
    if (dump_out->fault_present)
      DumpFaultAddress(dump_out, register_io_.get());
  }
  
  void MsdIntelDevice::DumpFault(DumpState* dump_out, uint32_t fault) {
    dump_out->fault_present = registers::AllEngineFault::valid(fault);
    dump_out->fault_engine = registers::AllEngineFault::engine(fault);
    dump_out->fault_src = registers::AllEngineFault::src(fault);
    dump_out->fault_type = registers::AllEngineFault::type(fault);
  }
  
  void MsdIntelDevice::DumpFaultAddress(DumpState* dump_out, magma::RegisterIo* register_io) {
    uint64_t val = registers::FaultTlbReadData::read(register_io);
    dump_out->fault_gpu_address = registers::FaultTlbReadData::addr(val);
    dump_out->global = registers::FaultTlbReadData::is_ggtt(val);
  }
  
  void MsdIntelDevice::DumpToString(std::vector<std::string>& dump_out) {
    DumpState dump_state;
    Dump(&dump_state);
    FormatDump(dump_state, dump_out);
  }
  
  void MsdIntelDevice::FormatDump(DumpState& dump_state, std::vector<std::string>& dump_out) {
    dump_out.clear();
  
    const char* build = magma::kDebug ? "DEBUG" : "RELEASE";
    const char* fmt =
        "---- GPU dump begin ----\n"
        "%s build\n"
        "Device id: 0x%x Revision: 0x%x\n"
        "RENDER_COMMAND_STREAMER\n"
        "sequence_number 0x%x\n"
        "active head pointer: 0x%llx";
    int size =
        std::snprintf(nullptr, 0, fmt, build, device_id(), revision(),
                      dump_state.render_cs.sequence_number, dump_state.render_cs.active_head_pointer);
    std::vector<char> buf(size + 1);
    std::snprintf(&buf[0], buf.size(), fmt, build, device_id(), revision(),
                  dump_state.render_cs.sequence_number, dump_state.render_cs.active_head_pointer);
    dump_out.push_back(&buf[0]);
  
    if (dump_state.fault_present) {
      fmt =
          "ENGINE FAULT DETECTED\n"
          "engine 0x%x src 0x%x type 0x%x gpu_address 0x%lx global %d";
      size = std::snprintf(nullptr, 0, fmt, dump_state.fault_engine, dump_state.fault_src,
                           dump_state.fault_type, dump_state.fault_gpu_address, dump_state.global);
      std::vector<char> buf(size + 1);
      std::snprintf(&buf[0], buf.size(), fmt, dump_state.fault_engine, dump_state.fault_src,
                    dump_state.fault_type, dump_state.fault_gpu_address, dump_state.global);
      dump_out.push_back(&buf[0]);
    } else {
      dump_out.push_back("No engine faults detected.");
    }
  
    bool is_mapped = false;
    std::vector<GpuMappingView*> mappings;
    GpuMappingView* fault_mapping;
    GpuMappingView* closest_mapping;
    const GpuMappingView* faulted_batch_mapping = nullptr;
    uint64_t closest_mapping_distance = UINT64_MAX;
  
    if (!dump_state.render_cs.inflight_batches.empty()) {
      dump_out.push_back("Inflight Batches:");
      for (auto batch : dump_state.render_cs.inflight_batches) {
        fmt = "  Batch %p, context %p, connection client_id %lu";
        auto context = batch->GetContext().lock().get();
        auto connection = context ? context->connection().lock() : nullptr;
        size =
            std::snprintf(nullptr, 0, fmt, batch, context, connection ? connection->client_id() : 0u);
        std::vector<char> buf(size + 1);
        std::snprintf(&buf[0], buf.size(), fmt, batch, context,
                      connection ? connection->client_id() : 0u);
        dump_out.push_back(&buf[0]);
  
        auto batch_mapping = batch->GetBatchMapping();
        if (!batch_mapping)
          continue;
  
        if (dump_state.render_cs.active_head_pointer >= batch_mapping->gpu_addr() &&
            dump_state.render_cs.active_head_pointer <
                batch_mapping->gpu_addr() + batch_mapping->length()) {
          dump_out.push_back("  FAULTING BATCH (active head ptr within this batch)");
          faulted_batch_mapping = batch_mapping;
        }
  
        if (!batch->IsCommandBuffer())
          continue;
  
        auto cmd_buf = static_cast<CommandBuffer*>(batch);
        cmd_buf->GetMappings(&mappings);
        for (const auto& mapping : mappings) {
          fmt =
              "    Mapping %p, buffer 0x%lx, gpu addr range [0x%lx, 0x%lx), "
              "offset 0x%lx, mapping length 0x%lx";
          gpu_addr_t mapping_start = mapping->gpu_addr();
          gpu_addr_t mapping_end = mapping->gpu_addr() + mapping->length();
          size = std::snprintf(nullptr, 0, fmt, mapping, mapping->BufferId(), mapping_start,
                               mapping_end, mapping->offset(), mapping->length());
          std::vector<char> buf(size + 1);
          std::snprintf(&buf[0], buf.size(), fmt, mapping, mapping->BufferId(), mapping_start,
                        mapping_end, mapping->offset(), mapping->length());
          dump_out.push_back(&buf[0]);
          if (dump_state.fault_gpu_address >= mapping_start &&
              dump_state.fault_gpu_address < mapping_end) {
            is_mapped = true;
            fault_mapping = mapping;
          } else if (dump_state.fault_gpu_address > mapping_end &&
                     dump_state.fault_gpu_address - mapping_end < closest_mapping_distance) {
            closest_mapping_distance = dump_state.fault_gpu_address - mapping_end;
            closest_mapping = mapping;
          }
        }
      }
    }
  
    if (is_mapped) {
      fmt = "Fault address appears to be within mapping %p addr [0x%lx, 0x%lx)";
      size = std::snprintf(nullptr, 0, fmt, fault_mapping, fault_mapping->gpu_addr(),
                           fault_mapping->gpu_addr() + fault_mapping->length());
      std::vector<char> buf(size + 1);
      std::snprintf(&buf[0], buf.size(), fmt, fault_mapping, fault_mapping->gpu_addr(),
                    fault_mapping->gpu_addr() + fault_mapping->length());
      dump_out.push_back(&buf[0]);
    } else {
      dump_out.push_back("Fault address does not appear to be mapped for any outstanding batch");
      if (closest_mapping_distance < UINT64_MAX) {
        fmt =
            "Fault address is 0x%lx past the end of mapping %p addr [0x%08lx, 0x%08lx), size "
            "0x%lx, buffer size 0x%lx";
        size = std::snprintf(nullptr, 0, fmt, closest_mapping_distance, closest_mapping,
                             closest_mapping->gpu_addr(),
                             closest_mapping->gpu_addr() + closest_mapping->length(),
                             closest_mapping->length(), closest_mapping->BufferSize());
        std::vector<char> buf(size + 1);
        std::snprintf(&buf[0], buf.size(), fmt, closest_mapping_distance, closest_mapping,
                      closest_mapping->gpu_addr(),
                      closest_mapping->gpu_addr() + closest_mapping->length(),
                      closest_mapping->length(), closest_mapping->BufferSize());
        dump_out.push_back(&buf[0]);
      }
    }
  
    if (faulted_batch_mapping) {
      dump_out.push_back("Batch instructions immediately surrounding the active head:");
      std::vector<uint32_t> batch_data;
      // dont early out because we always want to print the "dump end" line
      if (faulted_batch_mapping->Copy(&batch_data)) {
        uint64_t active_head_offset =
            dump_state.render_cs.active_head_pointer - faulted_batch_mapping->gpu_addr();
        DASSERT(active_head_offset <= faulted_batch_mapping->length());
        DASSERT(active_head_offset % sizeof(uint32_t) == 0);
        uint64_t total_dwords = faulted_batch_mapping->length() / sizeof(uint32_t);
        uint64_t active_head_dword = active_head_offset / sizeof(uint32_t);
        uint64_t start_dword = faulted_batch_mapping->offset();
        uint64_t end_dword = total_dwords - 1;
  
        uint32_t dwords_remaining = 0;
        bool end_of_batch = false;
        for (uint64_t i = start_dword; i < end_dword; i++) {
          if (dwords_remaining == 0) {
            InstructionDecoder::Id id;
            bool decoded = InstructionDecoder::Decode(batch_data[i], &id, &dwords_remaining);
            if (decoded) {
              fmt = "%s: ";
              size = std::snprintf(nullptr, 0, fmt, InstructionDecoder::name(id));
              buf = std::vector<char>(size + 1);
              std::snprintf(&buf[0], buf.size(), fmt, InstructionDecoder::name(id));
              dump_out.push_back(&buf[0]);
              end_of_batch = id == InstructionDecoder::Id::MI_BATCH_BUFFER_END;
            }
          }
  
          const char* prefix = "";
          const char* suffix = ",";
          if (i == active_head_dword) {
            prefix = "===>";
            suffix = "<===,";
          }
          if (dwords_remaining)
            --dwords_remaining;
  
          fmt = "%s0x%08lx%s";
          size = std::snprintf(nullptr, 0, fmt, prefix, batch_data[i], suffix);
          std::vector<char> buf(size + 1);
          std::snprintf(&buf[0], buf.size(), fmt, prefix, batch_data[i], suffix);
          dump_out.push_back(&buf[0]);
  
          if (end_of_batch)
            break;
        }
      } else {
        dump_out.push_back("Failed to map batch data");
      }
    }
  
    dump_out.push_back("---- GPU dump end ----");
  }
  