diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 50339cf..f1a7985 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,6 +7,7 @@ set(TEST_RUN86_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}) file (GLOB HEADERS "${TEST_RUN86_ROOT_DIR}/run.h" "${TEST_RUN86_ROOT_DIR}/nboxkrnl/pic.hpp" + "${TEST_RUN86_ROOT_DIR}/nboxkrnl/pit.hpp" ) file (GLOB SOURCES @@ -14,6 +15,7 @@ file (GLOB SOURCES "${TEST_RUN86_ROOT_DIR}/hook.cpp" "${TEST_RUN86_ROOT_DIR}/nboxkrnl/kernel.cpp" "${TEST_RUN86_ROOT_DIR}/nboxkrnl/pic.cpp" + "${TEST_RUN86_ROOT_DIR}/nboxkrnl/pit.cpp" "${TEST_RUN86_ROOT_DIR}/run.cpp" "${TEST_RUN86_ROOT_DIR}/test386.cpp" "${TEST_RUN86_ROOT_DIR}/test80186.cpp" diff --git a/test/nboxkrnl/kernel.cpp b/test/nboxkrnl/kernel.cpp index 7a09819..022944c 100644 --- a/test/nboxkrnl/kernel.cpp +++ b/test/nboxkrnl/kernel.cpp @@ -7,7 +7,8 @@ #include #include "pic.hpp" -#include "..\run.h" +#include "pit.hpp" +#include "../run.h" #define CONTIGUOUS_MEMORY_BASE 0x80000000 @@ -253,7 +254,12 @@ gen_nboxkrnl_test(const std::string &executable) return false; } + if (!LC86_SUCCESS(mem_init_region_io(cpu, 0x40, 4, true, io_handlers_t{ .fnw8 = pit_write_handler }, nullptr))) { + return false; + } + pic_reset(); + pit_reset(); // Load kernel exe into ram uint8_t *ram = get_ram_ptr(cpu); @@ -311,3 +317,17 @@ gen_nboxkrnl_test(const std::string &executable) return true; } + +lc86_status +run_nboxkrnl_test() +{ + timer_init(); + cpu_sync_state(cpu); + + while (true) { + lc86_status ret = cpu_run_until(cpu, pit_get_next_irq_time(get_now())); + if (ret != lc86_status::timeout) [[unlikely]] { + return ret; + } + } +} diff --git a/test/nboxkrnl/pic.cpp b/test/nboxkrnl/pic.cpp index 059a874..15fd62e 100644 --- a/test/nboxkrnl/pic.cpp +++ b/test/nboxkrnl/pic.cpp @@ -5,7 +5,7 @@ */ #include "pic.hpp" -#include "..\run.h" +#include "../run.h" #include diff --git a/test/nboxkrnl/pit.cpp b/test/nboxkrnl/pit.cpp new file mode 100644 index 0000000..94b82bf --- /dev/null +++ b/test/nboxkrnl/pit.cpp @@ -0,0 +1,196 @@ +/* + * pit barebones implementation + * + * ergo720 Copyright (c) 2023 + */ + +#include "pit.hpp" +#include "pic.hpp" +#include "../run.h" +#ifdef __linux__ +#include +#elif _WIN64 +#include "Windows.h" +#undef max +#endif +#include + + +// NOTE: on the xbox, the pit frequency is 6% lower than the default one, see https://xboxdevwiki.net/Porting_an_Operating_System_to_the_Xbox_HOWTO#Timer_Frequency +constexpr uint64_t pit_clock_freq = 1125000; +constexpr uint64_t ticks_per_second = 1000000; +static uint64_t tot_time, last_time; + +#ifdef __linux__ +void +timer_init() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + last_time = static_cast(tv.tv_sec) * static_cast(ticks_per_second) + static_cast(tv.tv_usec); +} +#elif _WIN64 +static uint64_t host_freq; + +void +timer_init() +{ + LARGE_INTEGER freq, now; + QueryPerformanceFrequency(&freq); + host_freq = freq.QuadPart; + QueryPerformanceCounter(&now); + last_time = now.QuadPart; +} +#endif + +uint64_t +get_now() +{ +#ifdef __linux__ + timeval tv; + gettimeofday(&tv, NULL); + uint64_t curr_time = static_cast(tv.tv_sec) * static_cast(ticks_per_second) + static_cast(tv.tv_usec); + return tot_time += (curr_time - last_time); +#elif _WIN64 + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + uint64_t elapsed_us = static_cast(now.QuadPart) - last_time; + last_time = now.QuadPart; + elapsed_us *= 1000000; + elapsed_us /= host_freq; + tot_time += elapsed_us; + return tot_time; +#else +#error "don't know how to implement the get_now function on this OS" +#endif +} + +static inline uint64_t +pit_counter_to_us() +{ + constexpr double time_scale = static_cast(ticks_per_second) / static_cast(pit_clock_freq); + return (uint64_t)(static_cast(pit.chan[0].counter) * time_scale); +} + +uint64_t +pit_get_next_irq_time(uint64_t now) +{ + if (pit.chan[0].timer_running) { + uint64_t next_time, pit_period = pit_counter_to_us(); + if (now - pit.chan[0].last_irq_time >= pit_period) { + pit.chan[0].last_irq_time = now; + next_time = pit_period; + + pic_lower_irq(0); + pic_raise_irq(0); + } + else { + next_time = pit.chan[0].last_irq_time + pit_period - now; + } + + return next_time; + } + + return std::numeric_limits::max(); +} + +static void +pit_start_timer(pit_channel_t *chan) +{ + chan->last_irq_time = get_now(); + chan->timer_running = 1; + cpu_set_timeout(cpu, pit_get_next_irq_time(chan->last_irq_time)); +} + +void +pit_write_handler(uint32_t port, const uint8_t value, void *opaque) +{ + uint8_t channel = port & 3; + + switch (channel) + { + case 3: { + channel = value >> 6; + uint8_t opmode = value >> 1 & 7, bcd = value & 1, access = value >> 4 & 3; + + switch (channel) + { + case 3: + std::printf("Read back command is not supported\n"); + cpu_exit(cpu); + break; + + case 0: + case 1: + case 2: { + pit_channel_t *chan = &pit.chan[channel]; + if (!access) { + std::printf("Counter latch command is not supported\n"); + cpu_exit(cpu); + } + else { + if (bcd) { + std::printf("BCD mode not supported\n"); + cpu_exit(cpu); + } + + chan->wmode = access; + chan->timer_mode = opmode; + if ((chan->timer_mode == 2) && (channel == 0)) { + pic_raise_irq(0); + } + } + break; + } + } + break; + } + break; + + case 0: + case 1: + case 2: { + pit_channel_t *chan = &pit.chan[channel]; + + switch (chan->wmode) + { + case 0: + case 1: + case 2: + std::printf("Read/Load mode must be LSB first MSB last\n"); + cpu_exit(cpu); + break; + + case 3: + if (chan->lsb_read) { + chan->counter = (static_cast(value) << 8) | chan->counter; + pit_start_timer(chan); + chan->lsb_read = 0; + } + else { + chan->counter = value; + chan->lsb_read = 1; + } + break; + } + break; + } + } +} + +static void +pit_channel_reset(pit_channel_t *chan) +{ + chan->counter = 0; + chan->timer_mode = 0; + chan->lsb_read = 0; + chan->timer_running = 0; +} + +void +pit_reset() +{ + for (pit_channel_t &chan : pit.chan) { + pit_channel_reset(&chan); + } +} diff --git a/test/nboxkrnl/pit.hpp b/test/nboxkrnl/pit.hpp new file mode 100644 index 0000000..ca41650 --- /dev/null +++ b/test/nboxkrnl/pit.hpp @@ -0,0 +1,29 @@ +/* + * pit declarations + * + * ergo720 Copyright (c) 2023 + */ + +#pragma once + +#include + + +struct pit_channel_t { + uint8_t timer_mode, wmode; + uint8_t timer_running, lsb_read; + uint16_t counter; + uint64_t last_irq_time; +}; + +struct pit_t { + pit_channel_t chan[3]; +}; + +inline pit_t pit; + +void timer_init(); +uint64_t get_now(); +uint64_t pit_get_next_irq_time(uint64_t now); +void pit_write_handler(uint32_t port, const uint8_t value, void *opaque); +void pit_reset(); diff --git a/test/run.cpp b/test/run.cpp index 5dccfc7..902c695 100644 --- a/test/run.cpp +++ b/test/run.cpp @@ -183,7 +183,14 @@ main(int argc, char **argv) cpu_set_flags(cpu, syntax | (use_dbg ? CPU_DBG_PRESENT : 0) | CPU_ABORT_ON_HLT); - lc86_status code = cpu_run(cpu); + lc86_status code; + if (test_num == 3) { + code = run_nboxkrnl_test(); + } + else { + code = cpu_run(cpu); + } + std::printf("Emulation terminated with status %d. The error was \"%s\"\n", static_cast(code), get_last_error().c_str()); cpu_free(cpu); diff --git a/test/run.h b/test/run.h index a41cb4e..b2ad066 100644 --- a/test/run.h +++ b/test/run.h @@ -12,3 +12,4 @@ bool gen_hook_test(); bool gen_dbg_test(); bool gen_nboxkrnl_test(const std::string &executable); void gen_test80186_test(const std::string &path, int syntax, int use_dbg); +lc86_status run_nboxkrnl_test();