Implemented basic i8254 PIT, replacing the system clock

This commit is contained in:
StrikerX3 2018-03-09 15:01:08 -03:00
parent d060f6ad01
commit bfc6fef53b
6 changed files with 143 additions and 116 deletions

View file

@ -0,0 +1,59 @@
#include "i8254.h"
#include <openxbox/thread.h>
namespace openxbox {
// TODO: Refer to QEMU if we ever need to complete this implementation:
// https://github.com/qemu/qemu/blob/master/hw/timer/i8254_common.c
// https://github.com/qemu/qemu/blob/master/hw/timer/i8254.c
// i8254 timer thread function
static uint32_t i8254ThreadFunc(void *data) {
i8254 *pit = (i8254 *)data;
pit->Run();
return 0;
}
i8254::i8254(Cpu *cpu, float tickRate)
: m_cpu(cpu)
, m_tickRate(tickRate)
, m_running(false)
{
}
void i8254::Reset() {
m_running = false;
}
void i8254::IORead(uint32_t addr, uint32_t *value, uint16_t size) {
*value = 0;
}
void i8254::IOWrite(uint32_t addr, uint32_t value, uint16_t size) {
// HACK: The Xbox always inits the PIT to the same value:
// Timer 0, Mode 2, 1ms interrupt interval.
// Rather than fully implement the PIC, we just wait for the command to
// start operating, and then simply issue IRQ 0 in a timer thread.
if (value == 0x34) {
m_running = true;
Thread_Create("[HW] i8254", i8254ThreadFunc, this);
}
}
void i8254::Run() {
using namespace std::chrono;
auto nextStop = high_resolution_clock::now();
auto interval = duration<long long, std::ratio<1, 1000000>>((long long)(1000000.0f / m_tickRate));
m_running = true;
while (m_running) {
m_cpu->Interrupt(0x30);
nextStop += interval;
std::this_thread::sleep_until(nextStop);
}
}
}

View file

@ -0,0 +1,30 @@
#pragma once
#include <cstdint>
#include "openxbox/cpu.h"
namespace openxbox {
#define PORT_PIT_DATA_0 0x40
#define PORT_PIT_DATA_1 0x41
#define PORT_PIT_DATA_2 0x42
#define PORT_PIT_COMMAND 0x43
class i8254
{
public:
i8254(Cpu *cpu, float tickRate = 1000.0f);
void Reset();
void Run();
void IORead(uint32_t addr, uint32_t *value, uint16_t size);
void IOWrite(uint32_t addr, uint32_t value, uint16_t size);
private:
Cpu *m_cpu;
float m_tickRate;
bool m_running;
};
}

View file

@ -1,33 +0,0 @@
#include "openxbox/hw/sysclock.h"
#include <chrono>
#include <thread>
namespace openxbox {
SystemClock::SystemClock(Cpu *cpu, float tickRate)
: m_cpu(cpu)
, m_tickRate(tickRate)
, m_running(false)
{
}
void SystemClock::Run() {
using namespace std::chrono;
auto nextStop = high_resolution_clock::now();
auto interval = duration<long long, std::ratio<1, 1000000>>((long long)(1000000.0f / m_tickRate));
m_running = true;
while (m_running) {
m_cpu->Interrupt(0x30);
nextStop += interval;
std::this_thread::sleep_until(nextStop);
}
}
void SystemClock::Stop() {
m_running = false;
}
}

View file

@ -1,41 +0,0 @@
#pragma once
#include "openxbox/cpu.h"
namespace openxbox {
/*!
* Represents the Xbox system clock.
*
* The system clock runs at 1000 ticks per second, generating IRQ 0 which
* corresponds to interrupt vector 0x30.
*
* The emulated system clock can be readjusted to tick at any rate, for
* debugging, testing and performance purposes. By default, it ticks 1000 times
* per second, just like the real hardware.
*/
class SystemClock {
public:
/*!
* Constructs a system clock that ticks at the specified rate, generating
* interrupts against the supplied CPU.
*/
SystemClock(Cpu *cpu, float tickRate = 1000.0f);
/*!
* Runs the system clock.
*/
void Run();
/*!
* Stops the system clock.
*/
void Stop();
private:
Cpu *m_cpu;
float m_tickRate;
bool m_running;
};
}

View file

@ -18,21 +18,12 @@ static uint32_t EmuCpuThreadFunc(void *data) {
return xbox->RunCpu();
}
// System clock thread function
static uint32_t EmuSysClockThreadFunc(void *data) {
SystemClock *sysClock = (SystemClock *)data;
sysClock->Run();
return 0;
}
/*!
* Constructor
*/
Xbox::Xbox(IOpenXBOXCPUModule *cpuModule)
: m_cpuModule(cpuModule)
{
m_sysClock = nullptr;
}
/*!
@ -159,11 +150,6 @@ int Xbox::Initialize(OpenXBOXSettings *settings)
// ----- Hardware ---------------------------------------------------------
// Initialize system clock
log_debug("Initializing System Clock\n");
m_sysClock = new SystemClock(m_cpu, settings->hw_sysclock_tickRate);
assert(m_sysClock != NULL);
// Determine which revisions of which components should be used for the
// specified hardware model
MCPXRevision mcpxRevision = MCPXRevisionFromHardwareModel(settings->hw_model);
@ -171,6 +157,10 @@ int Xbox::Initialize(OpenXBOXSettings *settings)
TVEncoder tvEncoder = TVEncoderFromHardwareModel(settings->hw_model);
log_debug("Initializing devices\n");
// Create PIT and PIC
m_i8254 = new i8254(m_cpu, settings->hw_sysclock_tickRate);
// Create busses
m_PCIBus = new PCIBus();
m_SMBus = new SMBus();
@ -249,9 +239,6 @@ int Xbox::Run() {
Thread *cpuIdleThread = Thread_Create("[HW] CPU", EmuCpuThreadFunc, this);
// --- Emulator subsystems ------------------------------------------------
// Start System Clock thread
Thread_Create("[HW] System clock", EmuSysClockThreadFunc, m_sysClock);
// TODO: start threads to handle other subsystems
// - One or more for each piece of hardware
@ -322,7 +309,8 @@ int Xbox::RunCpu()
// Handle reason for the CPU to exit
exit_info = m_cpu->GetExitInfo();
switch (exit_info->reason) {
case CPU_EXIT_SHUTDOWN: log_info("VM is shutting down\n"); Stop(); break;
case CPU_EXIT_HLT: log_info("CPU halted\n"); Stop(); break;
case CPU_EXIT_SHUTDOWN: log_info("VM is shutting down\n"); Stop(); break;
}
}
@ -330,14 +318,17 @@ int Xbox::RunCpu()
}
void Xbox::Stop() {
if (m_sysClock != nullptr) {
m_sysClock->Stop();
if (m_i8254 != nullptr) {
m_i8254->Reset();
}
m_should_run = false;
}
void Xbox::IORead(uint32_t addr, uint32_t *value, uint16_t size) {
log_spew("Xbox::IORead address = 0x%x, size = %d\n", addr, size);
log_debug("Xbox::IORead address = 0x%x, size = %d\n", addr, size);
// TODO: proper I/O port mapping
switch (addr) {
case 0x8008: { // TODO: Move 0x8008 TIMER to a device
if (size == sizeof(uint32_t)) {
@ -345,7 +336,7 @@ void Xbox::IORead(uint32_t addr, uint32_t *value, uint16_t size) {
auto t = std::chrono::high_resolution_clock::now();
*value = static_cast<uint32_t>(t.time_since_epoch().count() * 0.003579545);
}
break;
return;
}
case 0x80C0: { // TODO: Move 0x80C0 TV encoder to a device
if (size == sizeof(uint8_t)) {
@ -353,33 +344,54 @@ void Xbox::IORead(uint32_t addr, uint32_t *value, uint16_t size) {
m_field_pin = (m_field_pin + 1) & 1;
*value = m_field_pin << 5;
}
break;
}
}
// Pass the IO Read to the PCI Bus.
// This will handle devices with BARs set to IO addresses
if (m_PCIBus->IORead(addr, value, size)) {
return;
}
case PORT_PIT_DATA_0:
case PORT_PIT_DATA_1:
case PORT_PIT_DATA_2:
case PORT_PIT_COMMAND: {
m_i8254->IORead(addr, value, size);
return;
}
default:
// Pass the IO Read to the PCI Bus.
// This will handle devices with BARs set to IO addresses
if (m_PCIBus->IORead(addr, value, size)) {
return;
}
break;
}
log_warning("Unhandled I/O read! address = 0x%x, size = %d\n", addr, size);
log_warning("Unhandled I/O! address = 0x%x, size = %d, read\n", addr, size);
}
void Xbox::IOWrite(uint32_t addr, uint32_t value, uint16_t size) {
log_spew("Xbox::IOWrite address = 0x%x, size = %d, value = 0x%x\n", addr, size, value);
log_debug("Xbox::IOWrite address = 0x%x, size = %d, value = 0x%x\n", addr, size, value);
// Pass the IO Write to the PCI Bus.
// This will handle devices with BARs set to IO addresses
if (m_PCIBus->IOWrite(addr, value, size)) {
// TODO: proper I/O port mapping
switch (addr) {
case PORT_PIT_DATA_0:
case PORT_PIT_DATA_1:
case PORT_PIT_DATA_2:
case PORT_PIT_COMMAND:
m_i8254->IOWrite(addr, value, size);
return;
default:
// Pass the IO Write to the PCI Bus.
// This will handle devices with BARs set to IO addresses
if (m_PCIBus->IOWrite(addr, value, size)) {
return;
}
break;
}
log_warning("Unhandled I/O write! address = 0x%x, size = %d, value = 0x%08x\n", addr, size, value);
log_warning("Unhandled I/O! address = 0x%x, size = %d, write 0x%x\n", addr, size, value);
}
void Xbox::MMIORead(uint32_t addr, uint32_t *value, uint8_t size) {
log_spew("Xbox::MMIORead address = 0x%x, size = %d\n", addr, size);
log_debug("Xbox::MMIORead address = 0x%x, size = %d\n", addr, size);
if ((addr & (size - 1)) != 0) {
log_warning("Unaligned MMIO read! address = 0x%x, size = %d\n", addr, size);
@ -392,11 +404,11 @@ void Xbox::MMIORead(uint32_t addr, uint32_t *value, uint8_t size) {
return;
}
log_warning("Unhandled MMIO read! address = 0x%x, size = %d\n", addr, size);
log_warning("Unhandled MMIO! address = 0x%x, size = %d, read\n", addr, size);
}
void Xbox::MMIOWrite(uint32_t addr, uint32_t value, uint8_t size) {
log_spew("Xbox::MMIOWrite address = 0x%x, size = %d, value = 0x%x\n", addr, size, value);
log_debug("Xbox::MMIOWrite address = 0x%x, size = %d, value = 0x%x\n", addr, size, value);
if ((addr & (size - 1)) != 0) {
log_warning("Unaligned MMIO write! address = 0x%x, size = %d, value = 0x%x\n", addr, size, value);
@ -409,8 +421,7 @@ void Xbox::MMIOWrite(uint32_t addr, uint32_t value, uint8_t size) {
return;
}
log_warning("Unhandled MMIO write! address = 0x%x, size = %d, value = 0x%x\n", addr, size, value);
log_warning("Unhandled MMIO! address = 0x%x, size = %d, write 0x%x\n", addr, size, value);
}

View file

@ -20,7 +20,7 @@
#include "openxbox/thread.h"
#include "openxbox/settings.h"
#include "openxbox/hw/sysclock.h"
#include "openxbox/hw/i8254.h"
#include "openxbox/hw/smbus.h"
#include "openxbox/hw/pcibus.h"
#include "openxbox/hw/smc.h"
@ -50,7 +50,8 @@ protected:
char *m_rom;
MemoryRegion *m_memRegion;
SystemClock *m_sysClock;
i8254 *m_i8254;
SMCDevice *m_SMC;
SMBus *m_SMBus;
PCIBus *m_PCIBus;