mirror of
https://github.com/JorjBauer/aiie.git
synced 2024-12-28 21:29:34 +00:00
503 lines
12 KiB
C++
503 lines
12 KiB
C++
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <curses.h>
|
|
#include <termios.h>
|
|
#include <pthread.h>
|
|
|
|
#include "applevm.h"
|
|
#include "sdl-display.h"
|
|
#include "sdl-keyboard.h"
|
|
#include "sdl-mouse.h"
|
|
#include "sdl-speaker.h"
|
|
#include "sdl-paddles.h"
|
|
#include "nix-filemanager.h"
|
|
#include "sdl-printer.h"
|
|
#include "appleui.h"
|
|
#include "bios.h"
|
|
#include "nix-prefs.h"
|
|
#include "debugger.h"
|
|
|
|
#include "globals.h"
|
|
|
|
#include "timeutil.h"
|
|
|
|
BIOS bios;
|
|
Debugger debugger;
|
|
|
|
#define NB_ENABLE 1
|
|
#define NB_DISABLE 0
|
|
|
|
int send_rst = 0;
|
|
|
|
char disk1name[256] = "\0";
|
|
char disk2name[256] = "\0";
|
|
|
|
volatile bool wantSuspend = false;
|
|
volatile bool wantResume = false;
|
|
|
|
volatile bool cpuDebuggerRunning = false;
|
|
|
|
volatile bool cpuClockInitialized = false;
|
|
|
|
void doDebugging();
|
|
void readPrefs();
|
|
void writePrefs();
|
|
|
|
void sigint_handler(int n)
|
|
{
|
|
// If we want control-C to reset the machine, then set this here...
|
|
send_rst = 1;
|
|
|
|
// ((AppleVM*)g_vm)->disk6->disk[0]->dumpInfo();
|
|
}
|
|
|
|
void nonblock(int state)
|
|
{
|
|
struct termios ttystate;
|
|
|
|
//get the terminal state
|
|
tcgetattr(STDIN_FILENO, &ttystate);
|
|
|
|
if (state==NB_ENABLE)
|
|
{
|
|
//turn off canonical mode
|
|
ttystate.c_lflag &= ~ICANON;
|
|
//minimum of number input read.
|
|
ttystate.c_cc[VMIN] = 1;
|
|
}
|
|
else if (state==NB_DISABLE)
|
|
{
|
|
//turn on canonical mode
|
|
ttystate.c_lflag |= ICANON;
|
|
}
|
|
//set the terminal attributes.
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
|
|
|
|
}
|
|
|
|
uint8_t read(void *arg, uint16_t address)
|
|
{
|
|
// no action; this is a dummy function until we've finished initializing...
|
|
return 0x00;
|
|
}
|
|
|
|
void write(void *arg, uint16_t address, uint8_t v)
|
|
{
|
|
// no action; this is a dummy function until we've finished initializing...
|
|
}
|
|
|
|
static struct timespec runBIOS(struct timespec now)
|
|
{
|
|
static bool initialized = false;
|
|
static struct timespec startTime;
|
|
static struct timespec nextRuntime;
|
|
static uint64_t cycleCount = 0;
|
|
|
|
if (!initialized) {
|
|
do_gettime(&startTime);
|
|
do_gettime(&nextRuntime);
|
|
initialized = true;
|
|
}
|
|
|
|
timespec_add_us(&startTime, 100000*cycleCount, &nextRuntime); // FIXME: what's a good time here? 1/10 sec?
|
|
|
|
// Check if it's time to run - and if not, return how long it will
|
|
// be until we need to run
|
|
struct timespec diff = tsSubtract(nextRuntime, now);
|
|
if (diff.tv_sec > 0 || diff.tv_nsec > 0) {
|
|
// The caller can decide to nanosleep(&diff, NULL)
|
|
return diff;
|
|
}
|
|
|
|
cycleCount++;
|
|
|
|
if (!bios.loop()) {
|
|
printf("BIOS loop has exited\n");
|
|
g_biosInterrupt = false; // that's all she wrote!
|
|
}
|
|
|
|
// Reset timers!
|
|
cpuClockInitialized = false;
|
|
g_cpu->cycles = 0;
|
|
|
|
return diff;
|
|
}
|
|
|
|
static struct timespec runCPU(struct timespec now)
|
|
{
|
|
static struct timespec startTime;
|
|
static struct timespec nextInstructionTime;
|
|
|
|
if (!cpuClockInitialized) {
|
|
do_gettime(&startTime);
|
|
do_gettime(&nextInstructionTime);
|
|
cpuClockInitialized = true;
|
|
}
|
|
|
|
// Check for interrupt-like actions before running the CPU
|
|
if (wantSuspend) {
|
|
printf("CPU halted; suspending VM\n");
|
|
g_vm->Suspend("suspend.vm");
|
|
printf("... done; resuming CPU.\n");
|
|
wantSuspend = false;
|
|
}
|
|
if (wantResume) {
|
|
printf("CPU halted; resuming VM\n");
|
|
g_vm->Resume("suspend.vm");
|
|
printf("... done. resuming CPU.\n");
|
|
wantResume = false;
|
|
}
|
|
|
|
// Determine correct time for next CPU cycle
|
|
timespec_add_cycles(&startTime, g_cpu->cycles, &nextInstructionTime);
|
|
|
|
// Check if it's time to run - and if not, return how long it will be until we need to run
|
|
struct timespec diff = tsSubtract(nextInstructionTime, now);
|
|
if (diff.tv_sec > 0 || diff.tv_nsec > 0) {
|
|
// The caller can decide to nanosleep(&diff, NULL)
|
|
return diff;
|
|
}
|
|
|
|
// Run the CPU
|
|
uint8_t executed = 0;
|
|
bool debuggerWasActive = false;
|
|
if (debugger.active()) {
|
|
// With the debugger running, we need to single-step through
|
|
// instructions.
|
|
executed = g_cpu->Run(1);
|
|
debuggerWasActive = true;
|
|
} else {
|
|
// Otherwise we can run a bunch of instructions at once to
|
|
// save on the overhead.
|
|
executed = g_cpu->Run(24);
|
|
if (debuggerWasActive) {
|
|
cpuClockInitialized = false;
|
|
g_cpu->cycles = 0;
|
|
debuggerWasActive = false;
|
|
}
|
|
}
|
|
|
|
// The paddles need to be triggered in real-time on the CPU
|
|
// clock. That happens from the VM's CPU maintenance poller.
|
|
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
|
|
|
|
if (debugger.active()) {
|
|
debugger.step();
|
|
}
|
|
|
|
if (send_rst) {
|
|
cpuDebuggerRunning = true;
|
|
|
|
printf("Sending reset\n");
|
|
g_cpu->Reset();
|
|
|
|
send_rst = 0;
|
|
}
|
|
|
|
return diff;
|
|
}
|
|
|
|
#define TARGET_FPS 30
|
|
struct timespec runDisplay(struct timespec now)
|
|
{
|
|
static bool initialized = false;
|
|
static struct timespec startTime;
|
|
static struct timespec nextRuntime;
|
|
static uint64_t cycleCount = 0;
|
|
|
|
if (!initialized) {
|
|
do_gettime(&startTime);
|
|
do_gettime(&nextRuntime);
|
|
initialized = true;
|
|
}
|
|
|
|
timespec_add_us(&startTime, (1000000/TARGET_FPS)*cycleCount, &nextRuntime); // 1000000 uS/S and 30fps target
|
|
|
|
// Check if it's time to run - and if not, return how long it will
|
|
// be until we need to run
|
|
struct timespec diff = tsSubtract(nextRuntime, now);
|
|
if (diff.tv_sec > 0 || diff.tv_nsec > 0) {
|
|
// The caller can decide to nanosleep(&diff, NULL)
|
|
return diff;
|
|
}
|
|
|
|
cycleCount++;
|
|
|
|
if (!g_biosInterrupt) {
|
|
g_ui->blit();
|
|
g_vm->vmdisplay->lockDisplay();
|
|
if (g_vm->vmdisplay->needsRedraw()) {
|
|
AiieRect what = g_vm->vmdisplay->getDirtyRect();
|
|
g_vm->vmdisplay->didRedraw();
|
|
g_display->blit(what);
|
|
}
|
|
g_vm->vmdisplay->unlockDisplay();
|
|
|
|
// For SDL, I'm throwing the printer update in with the display update...
|
|
g_printer->update();
|
|
}
|
|
|
|
return diff;
|
|
}
|
|
|
|
|
|
void doDebugging()
|
|
{
|
|
char buf[25];
|
|
static time_t startAt = time(NULL);
|
|
static uint32_t loopCount = 0;
|
|
|
|
switch (g_debugMode) {
|
|
case D_SHOWFPS:
|
|
{
|
|
// display some FPS data
|
|
loopCount++;
|
|
uint32_t lenSecs = time(NULL) - startAt;
|
|
if (lenSecs >= 5) {
|
|
sprintf(buf, "%u FPS", loopCount / lenSecs);
|
|
g_display->debugMsg(buf);
|
|
startAt = time(NULL);
|
|
loopCount = 0;
|
|
}
|
|
}
|
|
break;
|
|
case D_SHOWMEMFREE:
|
|
// sprintf(buf, "%lu %u", FreeRamEstimate(), heapSize());
|
|
// g_display->debugMsg(buf);
|
|
break;
|
|
case D_SHOWPADDLES:
|
|
sprintf(buf, "%u %u", g_paddles->paddle0(), g_paddles->paddle1());
|
|
g_display->debugMsg(buf);
|
|
break;
|
|
case D_SHOWPC:
|
|
sprintf(buf, "%X", g_cpu->pc);
|
|
g_display->debugMsg(buf);
|
|
break;
|
|
case D_SHOWCYCLES:
|
|
sprintf(buf, "%llX", g_cpu->cycles);
|
|
g_display->debugMsg(buf);
|
|
break;
|
|
/*
|
|
case D_SHOWBATTERY:
|
|
// sprintf(buf, "BAT %d", analogRead(BATTERYPIN));
|
|
// g_display->debugMsg(buf);
|
|
break;
|
|
case D_SHOWTIME:
|
|
// sprintf(buf, "%.2d:%.2d:%.2d", hour(), minute(), second());
|
|
// g_display->debugMsg(buf);
|
|
break;*/
|
|
}
|
|
}
|
|
|
|
struct timespec runMaintenance(struct timespec now)
|
|
{
|
|
static bool initialized = false;
|
|
static struct timespec startTime;
|
|
static struct timespec nextRuntime;
|
|
static uint64_t cycleCount = 0;
|
|
|
|
if (!initialized) {
|
|
do_gettime(&startTime);
|
|
do_gettime(&nextRuntime);
|
|
initialized = true;
|
|
}
|
|
|
|
timespec_add_us(&startTime, 16667*cycleCount, &nextRuntime); // FIXME: what's a good time here? 60 Hz?
|
|
|
|
// Check if it's time to run - and if not, return how long it will
|
|
// be until we need to run
|
|
struct timespec diff = tsSubtract(nextRuntime, now);
|
|
if (diff.tv_sec > 0 || diff.tv_nsec > 0) {
|
|
// The caller can decide to nanosleep(&diff, NULL)
|
|
return diff;
|
|
}
|
|
|
|
cycleCount++;
|
|
if (!g_biosInterrupt) {
|
|
// If the BIOS is running, then let it handle the keyboard directly
|
|
g_keyboard->maintainKeyboard();
|
|
g_mouse->maintainMouse();
|
|
}
|
|
|
|
doDebugging();
|
|
g_ui->drawPercentageUIElement(UIePowerPercentage, 100);
|
|
|
|
return diff;
|
|
}
|
|
|
|
void loop()
|
|
{
|
|
struct timespec now;
|
|
do_gettime(&now);
|
|
|
|
struct timespec shortest;
|
|
|
|
static bool wasBios = false; // so we can tell when it's done
|
|
if (g_biosInterrupt) {
|
|
shortest = runBIOS(now);
|
|
wasBios = true;
|
|
} else {
|
|
if (wasBios) {
|
|
// bios has just exited
|
|
writePrefs();
|
|
|
|
// if we turned off debugMode, make sure to clear the debugMsg
|
|
if (g_debugMode == D_NONE) {
|
|
g_display->debugMsg("");
|
|
}
|
|
|
|
// Force the display to redraw
|
|
g_display->redraw(); // Redraw the UI
|
|
((AppleDisplay*)(g_vm->vmdisplay))->modeChange(); // force a full re-draw and blit
|
|
|
|
cpuClockInitialized = false; // force it to reset so it doesn't fast-forward
|
|
wasBios = false;
|
|
}
|
|
}
|
|
|
|
if (!g_biosInterrupt) {
|
|
shortest = runCPU(now); // about 13% CPU utilization on my laptop
|
|
}
|
|
struct timespec diff;
|
|
diff = runDisplay(now); // about 47% CPU utilization on my laptop
|
|
if (tsCompare(&shortest, &diff) > 0)
|
|
shortest = diff;
|
|
diff = runMaintenance(now); // about 1% CPU utilization on my laptop
|
|
if (tsCompare(&shortest, &diff) > 0)
|
|
shortest = diff;
|
|
|
|
// If they all have time remaining then sleep until one is ready
|
|
if (shortest.tv_sec || shortest.tv_nsec) {
|
|
nanosleep(&shortest, NULL);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
_init_darwin_shim();
|
|
|
|
SDL_Init(SDL_INIT_EVERYTHING);
|
|
|
|
g_speaker = new SDLSpeaker();
|
|
g_printer = new SDLPrinter();
|
|
|
|
// create the filemanager - the interface to the host file system.
|
|
g_filemanager = new NixFileManager();
|
|
|
|
g_display = new SDLDisplay();
|
|
// g_displayType = m_blackAndWhite;
|
|
|
|
g_ui = new AppleUI();
|
|
|
|
// paddles have to be created after g_display created the window
|
|
g_paddles = new SDLPaddles();
|
|
|
|
// Next create the virtual CPU. This needs the VM's MMU in order to run, but we don't have that yet.
|
|
g_cpu = new Cpu();
|
|
|
|
// Create the virtual machine. This may read from g_filemanager to get ROMs if necessary.
|
|
// (The actual Apple VM we've built has them compiled in, though.) It will create its virutal
|
|
// hardware (MMU, video driver, floppy, paddles, whatever).
|
|
g_vm = new AppleVM();
|
|
|
|
g_keyboard = new SDLKeyboard(g_vm->getKeyboard());
|
|
g_mouse = new SDLMouse();
|
|
|
|
// Now that the VM exists and it has created an MMU, we tell the CPU how to access memory through the MMU.
|
|
g_cpu->SetMMU(g_vm->getMMU());
|
|
|
|
// Now that all the virtual hardware is glued together, reset the VM
|
|
g_vm->Reset();
|
|
g_cpu->rst();
|
|
|
|
// g_display->blit();
|
|
g_display->redraw();
|
|
|
|
/* Load prefs & reset globals appropriately now */
|
|
readPrefs();
|
|
|
|
if (argc >= 2) {
|
|
printf("Inserting disk %s\n", argv[1]);
|
|
((AppleVM *)g_vm)->insertDisk(0, argv[1]);
|
|
strcpy(disk1name, argv[1]);
|
|
}
|
|
|
|
if (argc == 3) {
|
|
printf("Inserting disk %s\n", argv[2]);
|
|
((AppleVM *)g_vm)->insertDisk(1, argv[2]);
|
|
strcpy(disk2name, argv[2]);
|
|
}
|
|
|
|
// FIXME: fixed test disk...
|
|
// ((AppleVM *)g_vm)->insertHD(0, "hd32.img");
|
|
|
|
nonblock(NB_ENABLE);
|
|
|
|
signal(SIGINT, sigint_handler);
|
|
signal(SIGPIPE, SIG_IGN); // debugger might have a SIGPIPE happen if the remote end drops
|
|
|
|
g_speaker->begin();
|
|
|
|
printf("Starting loop\n");
|
|
while (1) {
|
|
loop();
|
|
}
|
|
}
|
|
|
|
void readPrefs()
|
|
{
|
|
NixPrefs np;
|
|
prefs_t p;
|
|
if (np.readPrefs(&p)) {
|
|
g_volume = p.volume;
|
|
g_displayType = p.displayType;
|
|
g_luminanceCutoff = p.luminanceCutoff;
|
|
|
|
g_debugMode = p.debug;
|
|
g_speed = (p.speed * (1023000/2)); // steps of half normal speed
|
|
if (g_speed < (1023000/2))
|
|
g_speed = (1023000/2);
|
|
if (p.disk1[0]) {
|
|
((AppleVM *)g_vm)->insertDisk(0, p.disk1);
|
|
strcpy(disk1name, p.disk1);
|
|
}
|
|
if (p.disk2[0]) {
|
|
((AppleVM *)g_vm)->insertDisk(1, p.disk2);
|
|
strcpy(disk2name, p.disk2);
|
|
}
|
|
|
|
if (p.hd1[0]) {
|
|
((AppleVM *)g_vm)->insertHD(0, p.hd1);
|
|
}
|
|
|
|
if (p.hd2[0]) {
|
|
((AppleVM *)g_vm)->insertHD(1, p.hd2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void writePrefs()
|
|
{
|
|
NixPrefs np;
|
|
prefs_t p;
|
|
|
|
p.magic = PREFSMAGIC;
|
|
p.prefsSize = sizeof(prefs_t);
|
|
p.version = PREFSVERSION;
|
|
|
|
p.volume = g_volume;
|
|
|
|
p.displayType = g_displayType;
|
|
p.luminanceCutoff = g_luminanceCutoff;
|
|
|
|
p.debug = g_debugMode;
|
|
p.speed = g_speed / (1023000/2);
|
|
strcpy(p.disk1, ((AppleVM *)g_vm)->DiskName(0));
|
|
strcpy(p.disk2, ((AppleVM *)g_vm)->DiskName(1));
|
|
strcpy(p.hd1, ((AppleVM *)g_vm)->HDName(0));
|
|
strcpy(p.hd2, ((AppleVM *)g_vm)->HDName(1));
|
|
|
|
bool ret = np.writePrefs(&p);
|
|
printf("writePrefs returns %s\n", ret ? "true" : "false");
|
|
}
|