| #include "sim/renode/renode_mpact.h" |
| |
| #include <cstdint> |
| #include <limits> |
| #include <string> |
| |
| #include "sim/kelvin_top.h" |
| #include "sim/renode/renode_debug_interface.h" |
| #include "absl/log/log.h" |
| #include "absl/strings/str_cat.h" |
| #include "mpact/sim/generic/type_helpers.h" |
| #include "mpact/sim/util/program_loader/elf_program_loader.h" |
| |
| // This function must be defined in the library. |
| extern kelvin::sim::renode::RenodeDebugInterface *CreateKelvinSim(std::string); |
| |
| extern kelvin::sim::renode::RenodeDebugInterface *CreateKelvinSim(std::string, |
| uint64_t, |
| uint64_t, |
| uint8_t **); |
| |
| // External "C" functions visible to Renode. |
| using kelvin::sim::renode::RenodeAgent; |
| using kelvin::sim::renode::RenodeCpuRegister; |
| |
| // Implementation of the C interface functions. They each forward the call to |
| // the corresponding method in RenodeAgent. |
| int32_t construct(int32_t max_name_length) { |
| return RenodeAgent::Instance()->Construct(max_name_length); |
| } |
| |
| int32_t construct_with_memory(int32_t max_name_length, |
| uint64_t memory_block_size_bytes, |
| uint64_t memory_size_bytes, |
| uint8_t **mem_block_ptr_list) { |
| return RenodeAgent::Instance()->Construct( |
| max_name_length, memory_block_size_bytes, memory_size_bytes, |
| mem_block_ptr_list); |
| } |
| |
| int32_t destruct(int32_t id) { return RenodeAgent::Instance()->Destroy(id); } |
| int32_t reset(int32_t id) { return RenodeAgent::Instance()->Reset(id); } |
| int32_t get_reg_info_size(int32_t id) { |
| return RenodeAgent::Instance()->GetRegisterInfoSize(id); |
| } |
| int32_t get_reg_info(int32_t id, int32_t index, char *name, void *info) { |
| if (info == nullptr) return -1; |
| return RenodeAgent::Instance()->GetRegisterInfo( |
| id, index, name, static_cast<RenodeCpuRegister *>(info)); |
| } |
| uint64_t load_executable(int32_t id, const char *elf_file_name, |
| int32_t *status) { |
| return RenodeAgent::Instance()->LoadExecutable(id, elf_file_name, status); |
| } |
| int32_t load_image(int32_t id, const char *file_name, uint64_t address) { |
| return RenodeAgent::Instance()->LoadImage(id, file_name, address); |
| } |
| int32_t read_register(int32_t id, uint32_t reg_id, uint64_t *value) { |
| return RenodeAgent::Instance()->ReadRegister(id, reg_id, value); |
| } |
| int32_t write_register(int32_t id, uint32_t reg_id, uint64_t value) { |
| return RenodeAgent::Instance()->WriteRegister(id, reg_id, value); |
| } |
| uint64_t read_memory(int32_t id, uint64_t address, char *buffer, |
| uint64_t length) { |
| return RenodeAgent::Instance()->ReadMemory(id, address, buffer, length); |
| } |
| uint64_t write_memory(int32_t id, uint64_t address, const char *buffer, |
| uint64_t length) { |
| return RenodeAgent::Instance()->WriteMemory(id, address, buffer, length); |
| } |
| uint64_t step(int32_t id, uint64_t num_to_step, int32_t *status) { |
| return RenodeAgent::Instance()->Step(id, num_to_step, status); |
| } |
| int32_t halt(int32_t id, int32_t *status) { |
| return RenodeAgent::Instance()->Halt(id, status); |
| } |
| |
| namespace kelvin::sim::renode { |
| |
| RenodeAgent *RenodeAgent::instance_ = nullptr; |
| uint32_t RenodeAgent::count_ = 0; |
| |
| // Create the debug instance by calling the factory function. |
| int32_t RenodeAgent::Construct(int32_t max_name_length) { |
| std::string name = absl::StrCat("renode", count_); |
| auto *dbg = CreateKelvinSim(name); |
| if (dbg == nullptr) { |
| return -1; |
| } |
| core_dbg_instances_.emplace(RenodeAgent::count_, dbg); |
| name_length_map_.emplace(RenodeAgent::count_, max_name_length); |
| return RenodeAgent::count_++; |
| } |
| |
| int32_t RenodeAgent::Construct(int32_t max_name_length, |
| uint64_t memory_block_size_bytes, |
| uint64_t memory_size_bytes, |
| uint8_t **mem_block_ptr_list) { |
| std::string name = absl::StrCat("renode", count_); |
| auto *dbg = CreateKelvinSim(name, memory_block_size_bytes, memory_size_bytes, |
| mem_block_ptr_list); |
| if (dbg == nullptr) { |
| return -1; |
| } |
| core_dbg_instances_.emplace(RenodeAgent::count_, dbg); |
| name_length_map_.emplace(RenodeAgent::count_, max_name_length); |
| return RenodeAgent::count_++; |
| } |
| |
| // Destroy the debug instance. |
| int32_t RenodeAgent::Destroy(int32_t id) { |
| // Check for valid instance. |
| auto dbg_iter = core_dbg_instances_.find(id); |
| if (dbg_iter == core_dbg_instances_.end()) return -1; |
| delete dbg_iter->second; |
| core_dbg_instances_.erase(dbg_iter); |
| return 0; |
| } |
| |
| int32_t RenodeAgent::Reset(int32_t id) { |
| // Check for valid instance. |
| auto dbg_iter = core_dbg_instances_.find(id); |
| if (dbg_iter == core_dbg_instances_.end()) return -1; |
| // For now, do nothing. |
| return 0; |
| } |
| |
| int32_t RenodeAgent::GetRegisterInfoSize(int32_t id) const { |
| // Check for valid instance. |
| auto dbg_iter = core_dbg_instances_.find(id); |
| if (dbg_iter == core_dbg_instances_.end()) return -1; |
| auto *dbg = dbg_iter->second; |
| return dbg->GetRenodeRegisterInfoSize(); |
| } |
| |
| int32_t RenodeAgent::GetRegisterInfo(int32_t id, int32_t index, char *name, |
| RenodeCpuRegister *info) { |
| // Check for valid instance. |
| if (info == nullptr) return -1; |
| auto dbg_iter = core_dbg_instances_.find(id); |
| if (dbg_iter == core_dbg_instances_.end()) return -1; |
| auto *dbg = dbg_iter->second; |
| int32_t max_len = name_length_map_.at(id); |
| auto result = dbg->GetRenodeRegisterInfo(index, max_len, name, *info); |
| if (!result.ok()) return -1; |
| return 0; |
| } |
| |
| // Read the register given by the id. |
| int32_t RenodeAgent::ReadRegister(int32_t id, uint32_t reg_id, |
| uint64_t *value) { |
| // Check for valid instance. |
| if (value == nullptr) return -1; |
| auto dbg_iter = core_dbg_instances_.find(id); |
| if (dbg_iter == core_dbg_instances_.end()) return -1; |
| // Read register. |
| auto *dbg = dbg_iter->second; |
| auto result = dbg->ReadRegister(reg_id); |
| if (!result.ok()) return -1; |
| *value = result.value(); |
| return 0; |
| } |
| |
| int32_t RenodeAgent::WriteRegister(int32_t id, uint32_t reg_id, |
| uint64_t value) { |
| // Check for valid instance. |
| auto dbg_iter = core_dbg_instances_.find(id); |
| if (dbg_iter == core_dbg_instances_.end()) return -1; |
| // Write register. |
| auto *dbg = dbg_iter->second; |
| auto result = dbg->WriteRegister(reg_id, value); |
| if (!result.ok()) return -1; |
| return 0; |
| } |
| |
| uint64_t RenodeAgent::ReadMemory(int32_t id, uint64_t address, char *buffer, |
| uint64_t length) { |
| // Check for valid desktop. |
| auto dbg_iter = core_dbg_instances_.find(id); |
| if (dbg_iter == core_dbg_instances_.end()) { |
| LOG(ERROR) << "No such core dbg instance: " << id; |
| return 0; |
| } |
| auto *dbg = dbg_iter->second; |
| auto res = dbg->ReadMemory(address, buffer, length); |
| if (!res.ok()) return 0; |
| return res.value(); |
| } |
| |
| uint64_t RenodeAgent::WriteMemory(int32_t id, uint64_t address, |
| const char *buffer, uint64_t length) { |
| // Check for valid desktop. |
| auto dbg_iter = core_dbg_instances_.find(id); |
| if (dbg_iter == core_dbg_instances_.end()) { |
| LOG(ERROR) << "No such core dbg instance: " << id; |
| return 0; |
| } |
| auto *dbg = dbg_iter->second; |
| auto res = dbg->WriteMemory(address, buffer, length); |
| if (!res.ok()) return 0; |
| return res.value(); |
| } |
| |
| uint64_t RenodeAgent::LoadExecutable(int32_t id, const char *file_name, |
| int32_t *status) { |
| // Check for valid desktop. |
| auto dbg_iter = core_dbg_instances_.find(id); |
| if (dbg_iter == core_dbg_instances_.end()) { |
| LOG(ERROR) << "No such core dbg instance: " << id; |
| *status = -1; |
| return 0; |
| } |
| // Instantiate loader and try to load the file. |
| auto *dbg = dbg_iter->second; |
| mpact::sim::util::ElfProgramLoader loader(dbg); |
| auto load_res = loader.LoadProgram(file_name); |
| if (!load_res.ok()) { |
| LOG(ERROR) << "Failed to load program: " << load_res.status().message(); |
| *status = -1; |
| return 0; |
| } |
| uint64_t entry = load_res.value(); |
| if (!dbg->WriteRegister("pc", entry).ok()) { |
| LOG(ERROR) << "Failed to write to the pc: " << load_res.status().message(); |
| *status = -1; |
| return 0; |
| } |
| *status = 0; |
| return entry; |
| } |
| |
| int32_t RenodeAgent::LoadImage(int32_t id, const char *file_name, |
| uint64_t address) { |
| // Get the debug interface. |
| auto dbg_iter = core_dbg_instances_.find(id); |
| if (dbg_iter == core_dbg_instances_.end()) { |
| LOG(ERROR) << "No such core dbg instance: " << id; |
| return -1; |
| } |
| auto *dbg = dbg_iter->second; |
| auto res = dbg->LoadImage(std::string(file_name), address); |
| if (!res.ok()) { |
| LOG(ERROR) << "Failed to load image: " << res.message(); |
| return -1; |
| } |
| return 0; |
| } |
| |
| uint64_t RenodeAgent::Step(int32_t id, uint64_t num_to_step, int32_t *status) { |
| // Set the default execution status |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| } |
| |
| // Get the core debug if object. |
| auto *dbg = RenodeAgent::Instance()->core_dbg(id); |
| // Is the debug interface valid? |
| if (dbg == nullptr) { |
| return 0; |
| } |
| if (num_to_step == 0) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kOk); |
| } |
| return 0; |
| } |
| // Check the previous halt reason. |
| auto halt_res = dbg->GetLastHaltReason(); |
| if (!halt_res.ok()) { |
| return 0; |
| } |
| // If the previous halt reason was a semihost halt request, then we shouldn't |
| // step any further. Just return with "waiting for interrupt" code. |
| using mpact::sim::generic::operator*; // NOLINT: used below. |
| if (halt_res.value() == *HaltReason::kSemihostHaltRequest) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| } |
| return 0; |
| } |
| // Perform the stepping. |
| uint32_t total_executed = 0; |
| while (num_to_step > 0) { |
| // Check how far to step, and make multiple calls if the number |
| // is greater than <int>::max(); |
| int step_count = (num_to_step > std::numeric_limits<int>::max()) |
| ? std::numeric_limits<int>::max() |
| : static_cast<int>(num_to_step); |
| auto res = dbg->Step(step_count); |
| // An error occurred. |
| if (!res.ok()) { |
| return total_executed; |
| } |
| int num_executed = res.value(); |
| total_executed += num_executed; |
| // Check if the execution was halted due to a semihosting halt request, |
| // i.e., program exit. |
| halt_res = dbg->GetLastHaltReason(); |
| if (!halt_res.ok()) { |
| return total_executed; |
| } |
| switch (halt_res.value()) { |
| case *HaltReason::kSemihostHaltRequest: |
| return total_executed; |
| break; |
| case *HaltReason::kSoftwareBreakpoint: |
| case *HaltReason::kHardwareBreakpoint: |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kStoppedAtBreakpoint); |
| } |
| return total_executed; |
| break; |
| case *HaltReason::kUserRequest: |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kOk); |
| } |
| return total_executed; |
| break; |
| case kHaltAbort: // `ebreak` custom halt reason |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| } |
| return total_executed; |
| break; |
| default: |
| break; |
| } |
| |
| // If we stepped fewer instructions than anticipated, stop stepping and |
| // return with no error. |
| if (num_executed < step_count) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kOk); |
| } |
| return total_executed; |
| } |
| num_to_step -= num_executed; |
| } |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kOk); |
| } |
| return total_executed; |
| } |
| |
| // Signal the simulator to halt. |
| int32_t RenodeAgent::Halt(int32_t id, int32_t *status) { |
| // Get the core debug if object. |
| auto *dbg = RenodeAgent::Instance()->core_dbg(id); |
| // Is the debug interface valid? |
| if (dbg == nullptr) { |
| return -1; |
| } |
| // Request halt. |
| auto halt_status = dbg->Halt(); |
| if (!halt_status.ok()) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| } |
| return -1; |
| } |
| // Get the halt status. |
| auto halt_res = dbg->GetLastHaltReason(); |
| if (!halt_res.ok()) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| } |
| return -1; |
| } |
| // Map the halt status appropriately. |
| using mpact::sim::generic::operator*; // NOLINT: used below. |
| if (status != nullptr) { |
| switch (halt_res.value()) { |
| case *HaltReason::kSemihostHaltRequest: |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| break; |
| case *HaltReason::kSoftwareBreakpoint: |
| *status = static_cast<int32_t>(ExecutionResult::kStoppedAtBreakpoint); |
| break; |
| case *HaltReason::kUserRequest: |
| *status = static_cast<int32_t>(ExecutionResult::kInterrupted); |
| break; |
| case *HaltReason::kNone: |
| *status = static_cast<int32_t>(ExecutionResult::kOk); |
| break; |
| case kelvin::sim::kHaltAbort: |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| break; |
| default: |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| } // namespace kelvin::sim::renode |