| // Copyright 2024 Google LLC |
| // |
| // 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 |
| // |
| // https://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 "mpact/sim/util/renode/renode_mpact.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstring> |
| #include <fstream> |
| #include <ios> |
| #include <limits> |
| #include <string> |
| |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "mpact/sim/generic/core_debug_interface.h" |
| #include "mpact/sim/generic/type_helpers.h" |
| #include "mpact/sim/util/memory/memory_interface.h" |
| #include "mpact/sim/util/renode/renode_debug_interface.h" |
| #include "mpact/sim/util/renode/renode_memory_access.h" |
| |
| using ::mpact::sim::generic::operator*; // NOLINT: used below (clang error). |
| using ::mpact::sim::util::MemoryInterface; |
| |
| // This function must be defined in the library. |
| extern ::mpact::sim::util::renode::RenodeDebugInterface* CreateMpactSim( |
| std::string name, std::string cpu_type, MemoryInterface*); |
| |
| using ::mpact::sim::util::renode::RenodeAgent; |
| using ::mpact::sim::util::renode::RenodeCpuRegister; |
| |
| // Implementation of the C interface functions. They each forward the call to |
| // the corresponding method in RenodeAgent. |
| int32_t construct(char* cpu_type, int32_t max_name_length) { |
| return RenodeAgent::Instance()->Construct(cpu_type, max_name_length, nullptr, |
| nullptr); |
| } |
| |
| int32_t construct_with_sysbus(char* cpu_type, int32_t max_name_length, |
| int32_t (*read_callback)(uint64_t, char*, |
| int32_t), |
| int32_t (*write_callback)(uint64_t, char*, |
| int32_t)) { |
| return RenodeAgent::Instance()->Construct(cpu_type, max_name_length, |
| read_callback, write_callback); |
| } |
| |
| int32_t connect(char* cpu_type, int32_t id, int32_t max_name_length) { |
| return RenodeAgent::Instance()->Connect(cpu_type, id, max_name_length, |
| nullptr, nullptr); |
| } |
| |
| int32_t connect_with_sysbus(char* cpu_type, int32_t id, int32_t max_name_length, |
| int32_t (*read_callback)(uint64_t, char*, int32_t), |
| int32_t (*write_callback)(uint64_t, char*, |
| int32_t)) { |
| return RenodeAgent::Instance()->Connect(cpu_type, id, max_name_length, |
| read_callback, write_callback); |
| } |
| |
| void destruct(int32_t id) { 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_elf(int32_t id, const char* elf_file_name, bool for_symbols_only, |
| int32_t* status) { |
| return RenodeAgent::Instance()->LoadExecutable(id, elf_file_name, |
| for_symbols_only, 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 set_config(int32_t id, const char* config_names[], |
| const char* config_values[], int32_t size) { |
| return RenodeAgent::Instance()->SetConfig(id, config_names, config_values, |
| size); |
| } |
| |
| int32_t set_irq_value(int32_t id, int32_t irq_num, bool irq_value) { |
| return RenodeAgent::Instance()->SetIrqValue(id, irq_num, irq_value); |
| } |
| |
| namespace mpact { |
| namespace sim { |
| namespace util { |
| namespace renode { |
| |
| using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason; |
| |
| RenodeAgent* RenodeAgent::instance_ = nullptr; |
| int32_t RenodeAgent::count_ = 0; |
| |
| // Create the debug instance by calling the factory function. |
| int32_t RenodeAgent::Construct(char* cpu_type, int32_t max_name_length, |
| int32_t (*read_callback)(uint64_t, char*, |
| int32_t), |
| int32_t (*write_callback)(uint64_t, char*, |
| int32_t)) { |
| std::string name = absl::StrCat("renode", count_); |
| auto* memory_access = new RenodeMemoryAccess(read_callback, write_callback); |
| auto* dbg = CreateMpactSim(name, cpu_type, memory_access); |
| if (dbg == nullptr) { |
| delete memory_access; |
| return -1; |
| } |
| // Make sure that we don't reuse an instance number that may have been created |
| // through Connect. |
| while (core_dbg_instances_.contains(RenodeAgent::count_)) { |
| RenodeAgent::count_++; |
| } |
| core_dbg_instances_.emplace(RenodeAgent::count_, dbg); |
| name_length_map_.emplace(RenodeAgent::count_, max_name_length); |
| renode_memory_access_.emplace(RenodeAgent::count_, memory_access); |
| return RenodeAgent::count_++; |
| } |
| |
| int32_t RenodeAgent::Connect(char* cpu_type, int32_t id, |
| int32_t max_name_length, |
| int32_t (*read_callback)(uint64_t, char*, int32_t), |
| int32_t (*write_callback)(uint64_t, char*, |
| int32_t)) { |
| // First check if the instance already exists. |
| auto iter = core_dbg_instances_.find(id); |
| if (iter != core_dbg_instances_.end()) { |
| // If memory callbacks are provided, don't overwrite any previous non-null |
| // callbacks. |
| auto* mem_access = renode_memory_access_.at(id); |
| // Replace any null callbacks. |
| if (!mem_access->has_read_fcn()) { |
| mem_access->set_read_fcn(read_callback); |
| } |
| if (!mem_access->has_write_fcn()) { |
| mem_access->set_write_fcn(write_callback); |
| } |
| return id; |
| } |
| |
| // The instance does not exist, so create a new debug instance. |
| std::string name = absl::StrCat("renode", id); |
| auto* memory_access = new RenodeMemoryAccess(read_callback, write_callback); |
| auto* dbg = CreateMpactSim(name, cpu_type, memory_access); |
| if (dbg == nullptr) { |
| delete memory_access; |
| return -1; |
| } |
| core_dbg_instances_.emplace(id, dbg); |
| name_length_map_.emplace(id, max_name_length); |
| renode_memory_access_.emplace(id, memory_access); |
| return id; |
| } |
| |
| // Destroy the debug instance. |
| void RenodeAgent::Destroy(int32_t id) { |
| // Check for valid instance. |
| auto dbg_iter = core_dbg_instances_.find(id); |
| // If it doesn't exist, it may already have been deleted, so just return. |
| if (dbg_iter == core_dbg_instances_.end()) return; |
| |
| delete dbg_iter->second; |
| core_dbg_instances_.erase(dbg_iter); |
| |
| // Delete the memory access shim. |
| auto mem_iter = renode_memory_access_.find(id); |
| if (mem_iter == renode_memory_access_.end()) return; |
| delete mem_iter->second; |
| renode_memory_access_.erase(mem_iter); |
| } |
| |
| 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) { |
| // 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 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) { |
| // 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 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, |
| bool for_symbols_only, int32_t* status) { |
| // 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; |
| *status = -1; |
| return 0; |
| } |
| // Instantiate loader. |
| auto* dbg = dbg_iter->second; |
| auto res = dbg->LoadExecutable(file_name, for_symbols_only); |
| if (!res.ok()) { |
| LOG(ERROR) << "Failed to load executable: " << res.status().message(); |
| *status = -1; |
| return 0; |
| } |
| *status = 0; |
| uint64_t entry = res.value(); |
| 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; |
| // Open up the image file. |
| std::ifstream image_file; |
| image_file.open(file_name, std::ios::in | std::ios::binary); |
| if (!image_file.good()) { |
| LOG(ERROR) << "LoadImage: Input file not in good state"; |
| return -1; |
| } |
| char buffer[kBufferSize]; |
| size_t gcount = 0; |
| uint64_t load_address = address; |
| do { |
| // Fill buffer. |
| image_file.read(buffer, kBufferSize); |
| // Get the number of bytes that was read. |
| gcount = image_file.gcount(); |
| // Write to the simulator memory. |
| auto res = dbg->WriteMemory(load_address, buffer, gcount); |
| // Check that the write succeeded, increment address if it did. |
| if (!res.ok()) { |
| LOG(ERROR) << "LoadImage: Memory write failed"; |
| return -1; |
| } |
| if (res.value() != gcount) { |
| LOG(ERROR) << "LoadImage: Memory write failed to write all the bytes"; |
| return -1; |
| } |
| load_address += gcount; |
| } while (image_file.good() && (gcount > 0)); |
| return 0; |
| } |
| |
| uint64_t RenodeAgent::Step(int32_t id, uint64_t num_to_step, int32_t* status) { |
| // Get the core debug if object. |
| auto* dbg = RenodeAgent::Instance()->core_dbg(id); |
| // Is the debug interface valid? |
| if (dbg == nullptr) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| } |
| return 0; |
| } |
| |
| if (num_to_step == 0) { |
| *status = static_cast<int32_t>(ExecutionResult::kOk); |
| return 0; |
| } |
| |
| // Check the previous halt reason. |
| auto halt_res = dbg->GetLastHaltReason(); |
| if (!halt_res.ok()) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| } |
| 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. |
| if (halt_res.value() == |
| *RenodeDebugInterface::HaltReason::kSemihostHaltRequest) { |
| *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()) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| } |
| 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()) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| } |
| return total_executed; |
| } |
| if (halt_res.value() == *HaltReason::kProgramDone) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kAborted); |
| } |
| return total_executed; |
| } |
| if (halt_res.value() == |
| *RenodeDebugInterface::HaltReason::kSemihostHaltRequest) { |
| if (status != nullptr) { |
| *status = static_cast<uint32_t>(ExecutionResult::kAborted); |
| } |
| return total_executed; |
| } |
| // Check if the execution ended at a software breakpoint. |
| if (halt_res.value() == |
| *RenodeDebugInterface::HaltReason::kSoftwareBreakpoint) { |
| if (status != nullptr) { |
| *status = static_cast<int32_t>(ExecutionResult::kStoppedAtBreakpoint); |
| } |
| return total_executed; |
| } |
| // 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; |
| } |
| |
| // Set configuration item. |
| int32_t RenodeAgent::SetConfig(int32_t id, const char* config_names[], |
| const char* config_values[], int size) { |
| // Get the core debug interface object. |
| auto* dbg = RenodeAgent::Instance()->core_dbg(id); |
| // Is the debug interface valid? |
| if (dbg == nullptr) { |
| return -1; |
| } |
| auto status = dbg->SetConfig(config_names, config_values, size); |
| if (!status.ok()) { |
| LOG(ERROR) << "SetConfig: " << status.message(); |
| return -1; |
| } |
| return 0; |
| } |
| |
| // Set irq value. |
| int32_t RenodeAgent::SetIrqValue(int32_t id, int32_t irq_num, bool irq_value) { |
| // Get the core debug interface object. |
| auto* dbg = RenodeAgent::Instance()->core_dbg(id); |
| // Is the debug interface valid? |
| if (dbg == nullptr) { |
| return -1; |
| } |
| auto status = dbg->SetIrqValue(irq_num, irq_value); |
| if (!status.ok()) { |
| LOG(ERROR) << "SetIrqValue: " << status.message(); |
| return -1; |
| } |
| return 0; |
| } |
| |
| } // namespace renode |
| } // namespace util |
| } // namespace sim |
| } // namespace mpact |