Add timers management.

This commit is contained in:
Maxim Poliakovski 2022-01-10 16:36:14 +01:00
parent 8efc61e1b9
commit 339db4a078
7 changed files with 313 additions and 21 deletions

View File

@ -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
View 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
View 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
View 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

View File

@ -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;

View File

@ -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,12 +299,22 @@ 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
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. */
#if 0
@ -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) {
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;

View File

@ -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;