mirror of
https://github.com/dingusdev/dingusppc.git
synced 2024-12-23 06:29:38 +00:00
Add timers management.
This commit is contained in:
parent
8efc61e1b9
commit
339db4a078
@ -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} $<TARGET_OBJECTS:debugger>
|
||||
add_executable(dingusppc ${SOURCES} $<TARGET_OBJECTS:core>
|
||||
$<TARGET_OBJECTS:cpu_ppc>
|
||||
$<TARGET_OBJECTS:debugger>
|
||||
$<TARGET_OBJECTS:devices>
|
||||
$<TARGET_OBJECTS:execution>
|
||||
$<TARGET_OBJECTS:machines>
|
||||
|
8
core/CMakeLists.txt
Normal file
8
core/CMakeLists.txt
Normal file
@ -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)
|
127
core/timermanager.cpp
Normal file
127
core/timermanager.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** Timer management system. */
|
||||
|
||||
#include <loguru.hpp>
|
||||
#include "timermanager.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
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<TimerInfo> 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<TimerInfo> 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;
|
||||
}
|
115
core/timermanager.h
Normal file
115
core/timermanager.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TIMER_MANAGER_H
|
||||
#define TIMER_MANAGER_H
|
||||
|
||||
#include <cinttypes>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define MIN_TIMEOUT_NS 200
|
||||
|
||||
#define NS_PER_SEC 1E9
|
||||
|
||||
#define USECS_TO_NSECS(us) (us) * 1000
|
||||
|
||||
typedef function<void()> timer_cb;
|
||||
|
||||
/** Extend std::priority_queue as suggested here:
|
||||
https://stackoverflow.com/a/36711682
|
||||
to be able to remove arbitrary elements.
|
||||
*/
|
||||
template <typename T, class Container = std::vector<T>, class Compare = std::less<typename Container::value_type>>
|
||||
class my_priority_queue : public std::priority_queue<T, Container, Compare> {
|
||||
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<TimerInfo>& l, const shared_ptr<TimerInfo>& 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<uint64_t()> &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<shared_ptr<TimerInfo>, vector<shared_ptr<TimerInfo>>, MyGtComparator> timer_queue;
|
||||
|
||||
function<uint64_t()> get_time_now;
|
||||
function<void()> notify_timer_changes;
|
||||
};
|
||||
|
||||
#endif // TIMER_MANAGER_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;
|
||||
|
@ -19,19 +19,21 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <core/timermanager.h>
|
||||
#include <loguru.hpp>
|
||||
#include "ppcemu.h"
|
||||
#include "ppcmmu.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <setjmp.h>
|
||||
#include <stdexcept>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <loguru.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
#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;
|
||||
|
@ -21,6 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// General opcodes for the processor - ppcopcodes.cpp
|
||||
|
||||
#include <core/timermanager.h>
|
||||
#include "ppcemu.h"
|
||||
#include "ppcmmu.h"
|
||||
#include <array>
|
||||
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user