mirror of
https://github.com/JorjBauer/aiie.git
synced 2024-11-19 11:31:00 +00:00
597 lines
15 KiB
C++
597 lines
15 KiB
C++
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <termios.h>
|
|
#include <pthread.h>
|
|
#include <sys/vt.h>
|
|
#include <sys/ioctl.h>
|
|
#include <signal.h>
|
|
|
|
#include "applevm.h"
|
|
#include "fb-display.h"
|
|
#include "linux-keyboard.h"
|
|
#include "linux-speaker.h"
|
|
#include "fb-paddles.h"
|
|
#include "nix-filemanager.h"
|
|
#include "linux-printer.h"
|
|
#include "appleui.h"
|
|
#include "bios.h"
|
|
#include "nix-prefs.h"
|
|
|
|
#include "globals.h"
|
|
|
|
#include "timeutil.h"
|
|
|
|
//#define SHOWFPS
|
|
//#define SHOWPC
|
|
//#define DEBUGCPU
|
|
//#define SHOWMEMPAGE
|
|
|
|
BIOS bios;
|
|
|
|
static struct timespec nextInstructionTime, startTime;
|
|
|
|
#define NB_ENABLE 1
|
|
#define NB_DISABLE 0
|
|
|
|
int send_rst = 0;
|
|
|
|
pthread_t cpuThreadID;
|
|
|
|
char disk1name[256] = "\0";
|
|
char disk2name[256] = "\0";
|
|
|
|
volatile bool wantSuspend = false;
|
|
volatile bool wantResume = false;
|
|
|
|
void doDebugging();
|
|
void readPrefs();
|
|
void writePrefs();
|
|
|
|
void sigint_handler(int n)
|
|
{
|
|
send_rst = 1;
|
|
}
|
|
|
|
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 void *cpu_thread(void *dummyptr) {
|
|
struct timespec currentTime;
|
|
struct timespec nextCycleTime;
|
|
int64_t nextSpeakerCycle = 0;
|
|
|
|
#if 0
|
|
int policy;
|
|
struct sched_param param;
|
|
pthread_getschedparam(pthread_self(), &policy, ¶m);
|
|
param.sched_priority = sched_get_priority_max(policy);
|
|
pthread_setschedparam(pthread_self(), policy, ¶m);
|
|
#endif
|
|
|
|
_init_darwin_shim();
|
|
do_gettime(&startTime);
|
|
printf("Start time: %lu,%lu\n", startTime.tv_sec, startTime.tv_nsec);
|
|
do_gettime(&nextInstructionTime);
|
|
|
|
printf("free-running\n");
|
|
while (1) {
|
|
if (g_biosInterrupt) {
|
|
printf("BIOS blocking\n");
|
|
while (g_biosInterrupt) {
|
|
usleep(100);
|
|
}
|
|
printf("BIOS block complete\n");
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
do_gettime(¤tTime);
|
|
|
|
/* The speaker is our priority. The CPU runs in batches anyway,
|
|
sometimes a little behind and sometimes a little ahead; but the
|
|
speaker has to be right on time. */
|
|
|
|
struct timespec diff;
|
|
|
|
#if 0
|
|
// Wait until nextSpeakerCycle
|
|
timespec_add_cycles(&startTime, nextSpeakerCycle, &nextCycleTime);
|
|
|
|
diff = tsSubtract(nextCycleTime, currentTime);
|
|
if (diff.tv_sec >= 0 || diff.tv_nsec >= 0) {
|
|
nanosleep(&diff, NULL);
|
|
}
|
|
|
|
// Speaker runs 48 cycles behind the CPU (an arbitrary number)
|
|
if (nextSpeakerCycle >= 48) {
|
|
timespec_add_cycles(&startTime, nextSpeakerCycle-48, &nextCycleTime);
|
|
uint64_t microseconds = nextCycleTime.tv_sec * 1000000 +
|
|
(double)nextCycleTime.tv_nsec / 1000.0;
|
|
g_speaker->maintainSpeaker(nextSpeakerCycle-48, microseconds);
|
|
}
|
|
|
|
// Bump speaker cycle for next go-round
|
|
nextSpeakerCycle++;
|
|
#endif
|
|
|
|
/* Next up is the CPU. */
|
|
|
|
// tsSubtract doesn't return negatives; it bounds at 0.
|
|
diff = tsSubtract(nextInstructionTime, currentTime);
|
|
|
|
uint8_t executed = 0;
|
|
if (diff.tv_sec == 0 && diff.tv_nsec == 0) {
|
|
#ifdef DEBUGCPU
|
|
executed = g_cpu->Run(1);
|
|
#else
|
|
executed = g_cpu->Run(24);
|
|
#endif
|
|
// calculate the real time that we should be at now, and schedule
|
|
// that as our next instruction time
|
|
timespec_add_cycles(&startTime, g_cpu->cycles, &nextInstructionTime);
|
|
|
|
// 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);
|
|
|
|
#ifdef DEBUGCPU
|
|
{
|
|
uint8_t p = g_cpu->flags;
|
|
printf("OP: $%02x A: %02x X: %02x Y: %02x PC: $%04x SP: %02x Flags: %c%cx%c%c%c%c%c\n",
|
|
g_vm->getMMU()->read(g_cpu->pc),
|
|
g_cpu->a, g_cpu->x, g_cpu->y, g_cpu->pc, g_cpu->sp,
|
|
p & (1<<7) ? 'N':' ',
|
|
p & (1<<6) ? 'V':' ',
|
|
p & (1<<4) ? 'B':' ',
|
|
p & (1<<3) ? 'D':' ',
|
|
p & (1<<2) ? 'I':' ',
|
|
p & (1<<1) ? 'Z':' ',
|
|
p & (1<<0) ? 'C':' '
|
|
);
|
|
}
|
|
#endif
|
|
|
|
if (send_rst) {
|
|
#if 0
|
|
printf("Scheduling suspend request...\n");
|
|
wantSuspend = true;
|
|
#endif
|
|
#if 0
|
|
printf("Scheduling resume resume request...\n");
|
|
wantResume = true;
|
|
#endif
|
|
|
|
#if 0
|
|
printf("Sending reset\n");
|
|
g_cpu->Reset();
|
|
|
|
// testing startup keyboard presses - perform Apple //e self-test
|
|
//g_vm->getKeyboard()->keyDepressed(RA);
|
|
//g_vm->Reset();
|
|
//g_cpu->Reset();
|
|
//((AppleVM *)g_vm)->insertDisk(0, "disks/DIAGS.DSK");
|
|
#endif
|
|
|
|
#if 0
|
|
// Swap disks
|
|
if (disk1name[0] && disk2name[0]) {
|
|
printf("Swapping disks\n");
|
|
|
|
printf("Inserting disk %s in drive 1\n", disk2name);
|
|
((AppleVM *)g_vm)->insertDisk(0, disk2name);
|
|
printf("Inserting disk %s in drive 2\n", disk1name);
|
|
((AppleVM *)g_vm)->insertDisk(1, disk1name);
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
MMU *mmu = g_vm->getMMU();
|
|
|
|
printf("PC: 0x%X\n", g_cpu->pc);
|
|
for (int i=g_cpu->pc; i<g_cpu->pc + 0x100; i++) {
|
|
printf("0x%X ", mmu->read(i));
|
|
}
|
|
printf("\n");
|
|
|
|
printf("Dropping to monitor\n");
|
|
// drop directly to monitor.
|
|
g_cpu->pc = 0xff69; // "call -151"
|
|
mmu->read(0xC054); // make sure we're in page 1
|
|
mmu->read(0xC056); // and that hires is off
|
|
mmu->read(0xC051); // and text mode is on
|
|
mmu->read(0xC08A); // and we have proper rom in place
|
|
mmu->read(0xc008); // main zero-page
|
|
mmu->read(0xc006); // rom from cards
|
|
mmu->write(0xc002 + mmu->read(0xc014)? 1 : 0, 0xff); // make sure aux ram read and write match
|
|
mmu->write(0x20, 0); // text window
|
|
mmu->write(0x21, 40);
|
|
mmu->write(0x22, 0);
|
|
mmu->write(0x23, 24);
|
|
mmu->write(0x33, '>');
|
|
mmu->write(0x48, 0); // from 0xfb2f: part of text init
|
|
#endif
|
|
|
|
send_rst = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int fd;
|
|
struct vt_stat vts;
|
|
int newVT;
|
|
int initialVT;
|
|
|
|
if ((fd=open("/dev/console", O_WRONLY)) < 0) {
|
|
perror("opening /dev/console");
|
|
exit(1);
|
|
}
|
|
|
|
ioctl(fd, VT_GETSTATE, &vts);
|
|
initialVT = vts.v_active; // find what VT we were on originally
|
|
ioctl(fd, VT_OPENQRY, &newVT);
|
|
if (newVT == -1) {
|
|
printf("No VTs available");
|
|
exit(1);
|
|
}
|
|
|
|
// Switch to new VT
|
|
ioctl(fd, VT_ACTIVATE, newVT);
|
|
ioctl(0, VT_WAITACTIVE, newVT);
|
|
|
|
printf("Now on VT %d\n", newVT);
|
|
|
|
// If we want stdout/stderr to move with us to the new VT, do this sorta thing, but to the right TTY
|
|
// freopen("/dev/tty2", "w", stdout);
|
|
// freopen("/dev/tty2", "w", stderr);
|
|
|
|
// Turn off cursor
|
|
system("echo 0 > /sys/class/graphics/fbcon/cursor_blink");
|
|
|
|
#if 0
|
|
// Timing consistency check
|
|
|
|
sleep(2); // kinda random, hopefully sloppy? - to make startTime != 0,0
|
|
printf("starting time consistency check\n");
|
|
do_gettime(&startTime);
|
|
for (int i=0; i<10000000; i++) {
|
|
|
|
// Calculate the time delta from startTime to cycle # i
|
|
timespec_add_cycles(&startTime, i, &nextInstructionTime);
|
|
|
|
// Recalculate the time difference between nextInstructionTime and startTime
|
|
struct timespec runtime = tsSubtract(nextInstructionTime, startTime);
|
|
|
|
// See if it's the same as cycles_since_time
|
|
double guesstimate = cycles_since_time(&runtime);
|
|
printf("cycle %d guesstimate %f\n", i, guesstimate);
|
|
if (guesstimate != i) {
|
|
printf("FAILED: cycle %d has guesstimate %f\n", i, guesstimate);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
printf("All ok\n");
|
|
|
|
exit(1);
|
|
#endif
|
|
|
|
g_speaker = new LinuxSpeaker();
|
|
g_printer = new LinuxPrinter();
|
|
|
|
// create the filemanager - the interface to the host file system.
|
|
g_filemanager = new NixFileManager();
|
|
|
|
g_display = new FBDisplay();
|
|
// g_displayType = m_blackAndWhite;
|
|
|
|
g_ui = new AppleUI();
|
|
|
|
// paddles have to be created after g_display created the window
|
|
g_paddles = new FBPaddles();
|
|
|
|
// 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 LinuxKeyboard(g_vm->getKeyboard());
|
|
|
|
// 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->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);
|
|
|
|
printf("creating CPU thread\n");
|
|
if (!pthread_create(&cpuThreadID, NULL, &cpu_thread, (void *)NULL)) {
|
|
printf("thread created\n");
|
|
// pthread_setschedparam(cpuThreadID, SCHED_RR, PTHREAD_MAX_PRIORITY);
|
|
}
|
|
|
|
while (1) {
|
|
if (g_biosInterrupt) {
|
|
printf("Invoking BIOS\n");
|
|
if (bios.runUntilDone()) {
|
|
// if it returned true, we have something to store
|
|
// persistently in EEPROM.
|
|
writePrefs();
|
|
}
|
|
printf("BIOS done\n");
|
|
|
|
// if we turned off debugMode, make sure to clear the debugMsg
|
|
if (g_debugMode == D_NONE) {
|
|
g_display->debugMsg("");
|
|
}
|
|
|
|
g_biosInterrupt = false;
|
|
|
|
// clear the CPU next-step counters
|
|
g_cpu->cycles = 0;
|
|
do_gettime(&startTime);
|
|
do_gettime(&nextInstructionTime);
|
|
|
|
// Drain the speaker queue (FIXME: a little hacky)
|
|
g_speaker->maintainSpeaker(-1, -1);
|
|
|
|
/* FIXME
|
|
// Force the display to redraw
|
|
((AppleDisplay*)(g_vm->vmdisplay))->modeChange();
|
|
*/
|
|
|
|
// Poll the keyboard before we start, so we can do selftest on startup
|
|
g_keyboard->maintainKeyboard();
|
|
}
|
|
|
|
|
|
static uint32_t usleepcycles = 16384; // step-down for display drawing. Dynamically updated based on FPS calculations.
|
|
|
|
g_ui->blit();
|
|
if (g_vm->vmdisplay->needsRedraw()) {
|
|
AiieRect what = g_vm->vmdisplay->getDirtyRect();
|
|
// make sure to clear the flag before drawing; there's no lock
|
|
// on didRedraw, so the other thread might update it
|
|
g_vm->vmdisplay->didRedraw();
|
|
g_display->blit(what);
|
|
}
|
|
|
|
g_printer->update();
|
|
g_keyboard->maintainKeyboard();
|
|
g_ui->drawPercentageUIElement(UIePowerPercentage, 100);
|
|
|
|
doDebugging();
|
|
|
|
// calculate FPS & dynamically step up/down as necessary
|
|
static time_t startAt = time(NULL);
|
|
static uint32_t loopCount = 0;
|
|
loopCount++;
|
|
uint32_t lenSecs = time(NULL) - startAt;
|
|
if (lenSecs >= 5) {
|
|
float fps = loopCount / lenSecs;
|
|
|
|
#ifdef SHOWFPS
|
|
char buf[25];
|
|
sprintf(buf, "%f FPS [delay %u]", fps, usleepcycles);
|
|
g_display->debugMsg(buf);
|
|
#endif
|
|
|
|
if (fps > 60) {
|
|
usleepcycles *= 2;
|
|
} else if (fps < 40) {
|
|
usleepcycles /= 2;
|
|
}
|
|
|
|
// reset the counter & we'll adjust again in 5 seconds
|
|
loopCount = 0;
|
|
startAt = time(NULL);
|
|
}
|
|
if (usleepcycles >= 2) {
|
|
usleep(usleepcycles);
|
|
}
|
|
|
|
#ifdef SHOWPC
|
|
{
|
|
char buf[25];
|
|
sprintf(buf, "%X", g_cpu->pc);
|
|
g_display->debugMsg(buf);
|
|
}
|
|
#endif
|
|
#ifdef SHOWMEMPAGE
|
|
{
|
|
char buf[40];
|
|
sprintf(buf, "AUX %c/%c BNK %d BSR %c/%c ZP %c 80 %c INT %c",
|
|
g_vm->auxRamRead?'R':'_',
|
|
g_vm->auxRamWrite?'W':'_',
|
|
g_vm->bank1,
|
|
g_vm->readbsr ? 'R':'_',
|
|
g_vm->writebsr ? 'W':'_',
|
|
g_vm->altzp ? 'Y':'_',
|
|
g_vm->_80store ? 'Y' : '_',
|
|
g_vm->intcxrom ? 'Y' : '_');
|
|
g_display->debugMsg(buf);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
}
|
|
|
|
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, "%X", 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;*/
|
|
}
|
|
}
|
|
|
|
void readPrefs()
|
|
{
|
|
NixPrefs np;
|
|
prefs_t p;
|
|
if (np.readPrefs(&p)) {
|
|
g_volume = p.volume;
|
|
g_displayType = p.displayType;
|
|
g_debugMode = p.debug;
|
|
g_prioritizeDisplay = p.priorityMode;
|
|
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.debug = g_debugMode;
|
|
p.priorityMode = g_prioritizeDisplay;
|
|
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");
|
|
}
|