From 339db4a078a693f8950ae2be55207dfb2a847488 Mon Sep 17 00:00:00 2001 From: Maxim Poliakovski Date: Mon, 10 Jan 2022 16:36:14 +0100 Subject: [PATCH] Add timers management. --- CMakeLists.txt | 5 +- core/CMakeLists.txt | 8 +++ core/timermanager.cpp | 127 +++++++++++++++++++++++++++++++++++++++++ core/timermanager.h | 115 +++++++++++++++++++++++++++++++++++++ cpu/ppc/ppcemu.h | 4 +- cpu/ppc/ppcexec.cpp | 72 +++++++++++++++++------ cpu/ppc/ppcopcodes.cpp | 3 +- 7 files changed, 313 insertions(+), 21 deletions(-) create mode 100644 core/CMakeLists.txt create mode 100644 core/timermanager.cpp create mode 100644 core/timermanager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cc6dcc..225503a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ if (DPPC_68K_DEBUGGER) add_subdirectory(thirdparty/capstone EXCLUDE_FROM_ALL) endif() +add_subdirectory("${PROJECT_SOURCE_DIR}/core") add_subdirectory("${PROJECT_SOURCE_DIR}/cpu/ppc/") add_subdirectory("${PROJECT_SOURCE_DIR}/debugger/") add_subdirectory("${PROJECT_SOURCE_DIR}/devices/") @@ -80,6 +81,7 @@ add_subdirectory(thirdparty/cubeb EXCLUDE_FROM_ALL) set(CLI11_ROOT ${PROJECT_SOURCE_DIR}/thirdparty/CLI11) include_directories("${PROJECT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/core" "${PROJECT_SOURCE_DIR}/devices" "${PROJECT_SOURCE_DIR}/cpu/ppc" "${PROJECT_SOURCE_DIR}/debugger" @@ -100,8 +102,9 @@ file(GLOB SOURCES "${PROJECT_SOURCE_DIR}/*.cpp" file(GLOB TEST_SOURCES "${PROJECT_SOURCE_DIR}/cpu/ppc/test/*.cpp") -add_executable(dingusppc ${SOURCES} $ +add_executable(dingusppc ${SOURCES} $ $ + $ $ $ $ diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 0000000..65de55a --- /dev/null +++ b/core/CMakeLists.txt @@ -0,0 +1,8 @@ +include_directories("${PROJECT_SOURCE_DIR}" + "${PROJECT_SOURCE_DIR}/thirdparty/loguru/" + ) + +file(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") + +add_library(core OBJECT ${SOURCES}) +target_link_libraries(core) \ No newline at end of file diff --git a/core/timermanager.cpp b/core/timermanager.cpp new file mode 100644 index 0000000..d6f6187 --- /dev/null +++ b/core/timermanager.cpp @@ -0,0 +1,127 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-22 divingkatae and maximum + (theweirdo) spatium + +(Contact divingkatae#1017 or powermax#2286 on Discord for more info) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +/** Timer management system. */ + +#include +#include "timermanager.h" + +#include +#include + +TimerManager* TimerManager::timer_manager; + +uint32_t TimerManager::add_oneshot_timer(uint64_t timeout, timer_cb cb) +{ + if (!timeout || timeout <= MIN_TIMEOUT_NS) { + LOG_F(WARNING, "One-shot timer too short, timeout=%llu ns", timeout); + } + + TimerInfo* ti = new TimerInfo; + + ti->id = ++this->id; + ti->timeout_ns = this->get_time_now() + timeout; + ti->interval_ns = 0; + ti->cb = cb; + + std::shared_ptr timer_desc(ti); + + // add new timer to the timer queue + this->timer_queue.push(timer_desc); + + // notify listeners about changes in the timer queue + this->notify_timer_changes(); + + return ti->id; +} + +uint32_t TimerManager::add_cyclic_timer(uint64_t interval, timer_cb cb) +{ + if (!interval || interval <= MIN_TIMEOUT_NS) { + LOG_F(WARNING, "Cyclic timer interval too short, timeout=%llu ns", interval); + } + + TimerInfo* ti = new TimerInfo; + + ti->id = ++this->id; + ti->timeout_ns = this->get_time_now() + interval; + ti->interval_ns = interval; + ti->cb = cb; + + std::shared_ptr timer_desc(ti); + + // add new timer to the timer queue + this->timer_queue.push(timer_desc); + + // notify listeners about changes in the timer queue + this->notify_timer_changes(); + + return ti->id; +} + +void TimerManager::cancel_timer(uint32_t id) +{ + TimerInfo* cur_timer = this->timer_queue.top().get(); + if (cur_timer->id == id) { + this->timer_queue.pop(); + } else { + this->timer_queue.remove_by_id(id); + } + this->notify_timer_changes(); +} + +uint64_t TimerManager::process_timers(uint64_t time_now) +{ + TimerInfo* cur_timer; + + if (this->timer_queue.empty()) { + return 0ULL; + } + + // scan for expired timers + cur_timer = this->timer_queue.top().get(); + while (cur_timer->timeout_ns <= time_now || + cur_timer->timeout_ns <= (time_now + MIN_TIMEOUT_NS)) { + // invoke timer callback + cur_timer->cb(); + + // re-arm cyclic timers + if (cur_timer->interval_ns) { + auto new_timer = this->timer_queue.top(); + new_timer->timeout_ns = time_now + cur_timer->interval_ns; + this->timer_queue.pop(); + this->timer_queue.push(new_timer); + } else { + // remove one-shot timers from queue + this->timer_queue.pop(); + } + + // process next timer + if (this->timer_queue.empty()) { + return 0ULL; + } + + cur_timer = this->timer_queue.top().get(); + } + + // return time slice in nanoseconds until next timer's expiry + return cur_timer->timeout_ns - time_now; +} \ No newline at end of file diff --git a/core/timermanager.h b/core/timermanager.h new file mode 100644 index 0000000..9b56660 --- /dev/null +++ b/core/timermanager.h @@ -0,0 +1,115 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-22 divingkatae and maximum + (theweirdo) spatium + +(Contact divingkatae#1017 or powermax#2286 on Discord for more info) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef TIMER_MANAGER_H +#define TIMER_MANAGER_H + +#include +#include +#include +#include +#include + +using namespace std; + +#define MIN_TIMEOUT_NS 200 + +#define NS_PER_SEC 1E9 + +#define USECS_TO_NSECS(us) (us) * 1000 + +typedef function timer_cb; + +/** Extend std::priority_queue as suggested here: + https://stackoverflow.com/a/36711682 + to be able to remove arbitrary elements. + */ +template , class Compare = std::less> +class my_priority_queue : public std::priority_queue { +public: + bool remove_by_id(const uint32_t id){ + auto it = std::find_if( + this->c.begin(), this->c.end(), [id](const T& el) { return el->id == id; }); + if (it != this->c.end()) { + this->c.erase(it); + std::make_heap(this->c.begin(), this->c.end(), this->comp); + return true; + } else { + return false; + } + }; +}; + +typedef struct TimerInfo { + uint32_t id; + uint64_t timeout_ns; // timer expiry + uint64_t interval_ns; // 0 for one-shot timers + timer_cb cb; // timer callback +} TimerInfo; + +// Custom comparator for sorting our timer queue in ascending order +class MyGtComparator { +public: + bool operator()(const shared_ptr& l, const shared_ptr& r) { + return l.get()->timeout_ns > r.get()->timeout_ns; + } +}; + +class TimerManager { +public: + static TimerManager* get_instance() { + if (!timer_manager) { + timer_manager = new TimerManager(); + } + return timer_manager; + }; + + // callback ´for retrieving current time + void set_time_now_cb(const function &cb) { + this->get_time_now = cb; + }; + + // callback for acknowledging time changes + void set_notify_changes_cb(const timer_cb &cb) { + this->notify_timer_changes = cb; + }; + + // creating and cancelling timers + uint32_t add_oneshot_timer(uint64_t timeout, timer_cb cb); + uint32_t add_cyclic_timer(uint64_t interval, timer_cb cb); + void cancel_timer(uint32_t id); + + uint64_t process_timers(uint64_t time_now); + +private: + static TimerManager* timer_manager; + TimerManager(){}; // private constructor to implement a singleton + + uint32_t id = 0; + + // timer queue + my_priority_queue, vector>, MyGtComparator> timer_queue; + + function get_time_now; + function notify_timer_changes; +}; + +#endif // TIMER_MANAGER_H \ No newline at end of file diff --git a/cpu/ppc/ppcemu.h b/cpu/ppc/ppcemu.h index b0596af..3aaa2a4 100644 --- a/cpu/ppc/ppcemu.h +++ b/cpu/ppc/ppcemu.h @@ -202,7 +202,8 @@ enum class BB_end_kind { BB_NONE = 0, /* no basic block end is reached */ BB_BRANCH = 1, /* a branch instruction is encountered */ BB_EXCEPTION, /* an exception is occured */ - BB_RFI /* the rfi instruction is encountered */ + BB_RFI, /* the rfi instruction is encountered */ + BB_TIMER, /* timer queue changed */ }; enum CR_select : int32_t { @@ -297,7 +298,6 @@ extern BB_end_kind bb_kind; extern jmp_buf exc_env; -extern bool grab_exception; extern bool grab_return; extern bool power_on; diff --git a/cpu/ppc/ppcexec.cpp b/cpu/ppc/ppcexec.cpp index 3b0f711..510c7af 100644 --- a/cpu/ppc/ppcexec.cpp +++ b/cpu/ppc/ppcexec.cpp @@ -19,19 +19,21 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#include +#include +#include "ppcemu.h" +#include "ppcmmu.h" + #include +#include #include #include #include #include #include #include -#include #include -#include "ppcemu.h" -#include "ppcmmu.h" - #define NEW_TBR_UPDATE_ALGO using namespace std; @@ -46,7 +48,6 @@ SetPRS ppc_state; bool rc_flag = 0; // Record flag bool oe_flag = 0; // Overflow flag -bool grab_exception; bool grab_return; bool grab_breakpoint; @@ -298,11 +299,21 @@ uint64_t get_virt_time_ns() return g_icycles << icnt_factor; } -int process_events() +uint64_t process_events() { - // dummy implementation that schedules execution - // of another 10.000 instructions - return g_icycles + 10000; + uint64_t slice_ns = TimerManager::get_instance()->process_timers(get_virt_time_ns()); + if (slice_ns == 0) { + // execute 10.000 cycles + // if there are no pending timers + return g_icycles + 10000; + } + return g_icycles + ((slice_ns + (1 << icnt_factor)) >> icnt_factor); +} + +void force_cycle_counter_reload() +{ + // tell the interpreter loop to reload cycle counter + bb_kind = BB_end_kind::BB_TIMER; } /** Execute PPC code as long as power is on. */ @@ -399,8 +410,15 @@ static void ppc_exec_inner() if (bb_kind != BB_end_kind::BB_NONE) { // execution block ended ? if (!power_on) break; - // check the reason for the block end - //eb_last = false; + // reload cycle counter if requested + if (bb_kind == BB_end_kind::BB_TIMER) { + max_cycles = process_events(); + ppc_state.pc += 4; + pc_real += 4; + ppc_set_cur_instruction(pc_real); + bb_kind = BB_end_kind::BB_NONE; + continue; + } // define next execution block eb_start = ppc_next_instruction_address; eb_end = (eb_start + PAGE_SIZE) & PAGE_MASK; @@ -486,7 +504,11 @@ void ppc_exec_single() mmu_translate_imem(ppc_state.pc); ppc_main_opcode(); if (bb_kind != BB_end_kind::BB_NONE) { - ppc_state.pc = ppc_next_instruction_address; + if (bb_kind == BB_end_kind::BB_TIMER) { + ppc_state.pc += 4; + } else { + ppc_state.pc = ppc_next_instruction_address; + } bb_kind = BB_end_kind::BB_NONE; } else { ppc_state.pc += 4; @@ -585,8 +607,15 @@ static void ppc_exec_until_inner(const uint32_t goal_addr) //if (eb_last) { if (bb_kind != BB_end_kind::BB_NONE) { // execution block ended ? - // check the reason for the block end - //eb_last = false; + // reload cycle counter if requested + if (bb_kind == BB_end_kind::BB_TIMER) { + max_cycles = process_events(); + ppc_state.pc += 4; + pc_real += 4; + ppc_set_cur_instruction(pc_real); + bb_kind = BB_end_kind::BB_NONE; + continue; + } // define next execution block eb_start = ppc_next_instruction_address; eb_end = (eb_start + PAGE_SIZE) & PAGE_MASK; @@ -719,8 +748,15 @@ static void ppc_exec_dbg_inner(const uint32_t start_addr, const uint32_t size) //if (eb_last) { if (bb_kind != BB_end_kind::BB_NONE) { // execution block ended ? - // check the reason for the block end - //eb_last = false; + // reload cycle counter if requested + if (bb_kind == BB_end_kind::BB_TIMER) { + max_cycles = process_events(); + ppc_state.pc += 4; + pc_real += 4; + ppc_set_cur_instruction(pc_real); + bb_kind = BB_end_kind::BB_NONE; + continue; + } // define next execution block eb_start = ppc_next_instruction_address; eb_end = (eb_start + PAGE_SIZE) & PAGE_MASK; @@ -1016,6 +1052,10 @@ void ppc_cpu_init(MemCtrlBase* mem_ctrl, uint32_t cpu_version) { tbr_factor = 4; #endif + // initialize emulator timers + TimerManager::get_instance()->set_time_now_cb(&get_virt_time_ns); + TimerManager::get_instance()->set_notify_changes_cb(&force_cycle_counter_reload); + // initialize time base facility g_icycles = 0; icnt_factor = 4; diff --git a/cpu/ppc/ppcopcodes.cpp b/cpu/ppc/ppcopcodes.cpp index f08c559..ee65dcd 100644 --- a/cpu/ppc/ppcopcodes.cpp +++ b/cpu/ppc/ppcopcodes.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . // General opcodes for the processor - ppcopcodes.cpp +#include #include "ppcemu.h" #include "ppcmmu.h" #include @@ -858,8 +859,6 @@ void dppc_interpreter::ppc_mfspr() { ppc_state.gpr[reg_d] = ppc_state.spr[ref_spr]; } -#define NS_PER_SEC 1E9 - static inline uint64_t calc_tbr_value() { uint64_t diff = get_virt_time_ns() - tbr_wr_timestamp;