bios rework in SDL base

This commit is contained in:
Jorj Bauer 2020-12-28 14:23:47 -05:00
parent 16fbb37f90
commit 538898d371
5 changed files with 836 additions and 642 deletions

858
bios.cpp
View File

@ -14,7 +14,7 @@ extern Bounce resetButtonDebouncer;
extern void runDebouncer();
#endif
// Experimenting with using EXTMEM to cache all the filenames in a directory
// using EXTMEM to cache all the filenames in a directory
#ifndef TEENSYDUINO
#define EXTMEM
#endif
@ -28,6 +28,24 @@ EXTMEM char cachedFilter[BIOS_MAXPATH] = {0};
EXTMEM struct _cacheEntry biosCache[BIOSCACHESIZE];
uint16_t numCacheEntries = 0;
// When selecting files...
char fileFilter[16]; // FIXME length & Strcpy -> strncpy
uint16_t fileSelectionFor; // define what the returned name is for
// menu screen enums
enum {
BIOS_AIIE = 0,
BIOS_VM = 1,
BIOS_HARDWARE = 2,
BIOS_DISKS = 3,
BIOS_ABOUT = 4,
BIOS_PADDLES = 5,
BIOS_SELECTFILE = 6,
BIOS_DONE = 99,
};
enum {
ACT_EXIT = 1,
@ -81,7 +99,7 @@ const char *staticPathConcat(const char *rootPath, const char *filePath)
BIOS::BIOS()
{
selectedMenu = 1;
selectedMenu = BIOS_VM;
selectedMenuItem = 0;
selectedFile = -1;
@ -124,7 +142,470 @@ void BIOS::DrawMenuBar()
}
}
bool BIOS::loop()
{
static bool needsinit = true;
if (needsinit) {
g_filemanager->getRootPath(rootPath, sizeof(rootPath));
needsinit = false;
}
static bool needsRedraw = true;
if (selectedMenu == BIOS_DONE) {
// We're returning to the bios a second time
selectedMenu = BIOS_VM;
needsRedraw = true;
}
#ifdef TEENSYDUINO
if (resetButtonDebouncer.read() == LOW) {
// wait until it's no longer pressed
while (resetButtonDebouncer.read() == LOW)
runDebouncer();
delay(100); // wait long enough for it to debounce
return BIOS_DONE;
}
#endif
bool hitReturn = false;
uint16_t rv;
if (g_keyboard->kbhit()) {
switch (g_keyboard->read()) {
case PK_DARR:
selectedMenuItem++; // modded by current action
needsRedraw = true;
break;
case PK_UARR:
selectedMenuItem--; // modded by current action
needsRedraw = true;
break;
case PK_RARR:
selectedMenu++;
selectedMenu %= NUM_TITLES;
needsRedraw = true;
break;
case PK_LARR:
selectedMenu--;
if (selectedMenu < 0) {
selectedMenu = NUM_TITLES-1;
}
needsRedraw = true;
break;
case PK_RET:
hitReturn = true;
needsRedraw = true;
break;
default:
break;
}
}
switch (selectedMenu) {
case BIOS_AIIE:
rv = AiieMenuHandler(needsRedraw, hitReturn);
break;
case BIOS_VM:
rv = VmMenuHandler(needsRedraw, hitReturn);
break;
case BIOS_HARDWARE:
rv = HardwareMenuHandler(needsRedraw, hitReturn);
break;
case BIOS_DISKS:
rv = DisksMenuHandler(needsRedraw, hitReturn);
break;
case BIOS_ABOUT:
rv = AboutScreenHandler(needsRedraw, hitReturn);
break;
case BIOS_PADDLES:
rv = PaddlesScreenHandler(needsRedraw, hitReturn);
break;
case BIOS_SELECTFILE:
rv = SelectFileScreenHandler(needsRedraw, hitReturn);
break;
}
if (rv != selectedMenu) {
needsRedraw = true;
selectedMenu = rv;
}
else
needsRedraw = false; // assume the handler drew
return ((selectedMenu == BIOS_DONE) ? false : true);
}
uint16_t BIOS::AiieMenuHandler(bool needsRedraw, bool performAction)
{
static bool localRedraw = true;
if (selectedMenuItem < 0)
selectedMenuItem = sizeof(aiieActions)-1;
selectedMenuItem %= sizeof(aiieActions);
if (needsRedraw || localRedraw) {
g_display->clrScr();
DrawMenuBar();
DrawAiieMenu();
g_display->flush();
localRedraw = false;
}
if (performAction) {
// there is only ACT_ABOUT
return BIOS_ABOUT;
}
return BIOS_AIIE;
}
uint16_t BIOS::VmMenuHandler(bool needsRedraw, bool performAction)
{
static bool localRedraw = true;
if (selectedMenuItem < 0)
selectedMenuItem = sizeof(vmActions)-1;
selectedMenuItem %= sizeof(vmActions);
if (needsRedraw || localRedraw) {
g_display->clrScr();
DrawMenuBar();
DrawVMMenu();
g_display->flush();
localRedraw = false;
}
if (performAction) {
if (isActionActive(vmActions[selectedMenuItem])) {
switch (vmActions[selectedMenuItem]) {
case ACT_EXIT:
return BIOS_DONE;
case ACT_RESET:
WarmReset();
return BIOS_DONE;
case ACT_COLDBOOT:
ColdReboot();
return BIOS_DONE;
case ACT_MONITOR:
((AppleVM *)g_vm)->Monitor();
return BIOS_DONE;
case ACT_DEBUG:
g_debugMode++;
g_debugMode %= 9; // FIXME: abstract max #
localRedraw = true;
return BIOS_VM;
case ACT_SUSPEND:
g_display->clrScr();
g_display->drawString(M_SELECTED, 80, 100,"Suspending VM...");
g_display->flush();
// CPU is already suspended, so this is safe...
((AppleVM *)g_vm)->Suspend("suspend.vm");
localRedraw = true;
return BIOS_VM;
case ACT_RESTORE:
g_display->clrScr();
g_display->drawString(M_SELECTED, 80, 100,"Resuming VM...");
g_display->flush();
((AppleVM *)g_vm)->Resume("suspend.vm");
return BIOS_DONE;
}
}
}
return BIOS_VM;
}
uint16_t BIOS::HardwareMenuHandler(bool needsRedraw, bool performAction)
{
static bool localRedraw = true;
if (selectedMenuItem < 0)
selectedMenuItem = sizeof(hardwareActions)-1;
selectedMenuItem %= sizeof(hardwareActions);
if (needsRedraw || localRedraw) {
g_display->clrScr();
DrawMenuBar();
DrawHardwareMenu();
g_display->flush();
localRedraw = false;
}
if (performAction) {
if (isActionActive(hardwareActions[selectedMenuItem])) {
switch (hardwareActions[selectedMenuItem]) {
case ACT_DISPLAYTYPE:
g_displayType++;
g_displayType %= 4; // FIXME: abstract max #
((AppleDisplay*)g_display)->displayTypeChanged();
localRedraw = true;
break;
case ACT_SPEED:
currentCPUSpeedIndex++;
currentCPUSpeedIndex %= 4;
switch (currentCPUSpeedIndex) {
case CPUSPEED_HALF:
g_speed = 1023000/2;
break;
case CPUSPEED_DOUBLE:
g_speed = 1023000*2;
break;
case CPUSPEED_QUAD:
g_speed = 1023000*4;
break;
default:
g_speed = 1023000;
break;
}
localRedraw = true;
break;
case ACT_PADX_INV:
g_invertPaddleX = !g_invertPaddleX;
#ifdef TEENSYDUINO
((TeensyPaddles *)g_paddles)->setRev(g_invertPaddleX, g_invertPaddleY);
#endif
localRedraw = true;
break;
case ACT_PADY_INV:
g_invertPaddleY = !g_invertPaddleY;
#ifdef TEENSYDUINO
((TeensyPaddles *)g_paddles)->setRev(g_invertPaddleX, g_invertPaddleY);
#endif
localRedraw = true;
break;
case ACT_PADDLES:
return BIOS_PADDLES;
case ACT_VOLPLUS:
g_volume ++;
if (g_volume > 15) {
g_volume = 15;
}
localRedraw = true;
break;
case ACT_VOLMINUS:
g_volume--;
if (g_volume < 0) {
g_volume = 0;
}
localRedraw = true;
break;
}
}
}
return BIOS_HARDWARE;
}
uint16_t BIOS::DisksMenuHandler(bool needsRedraw, bool performAction)
{
static bool localRedraw = true;
if (selectedMenuItem < 0)
selectedMenuItem = sizeof(diskActions)-1;
selectedMenuItem %= sizeof(diskActions);
if (needsRedraw || localRedraw) {
g_display->clrScr();
DrawMenuBar();
DrawDisksMenu();
g_display->flush();
localRedraw = false;
}
if (performAction) {
if (isActionActive(diskActions[selectedMenuItem])) {
switch (diskActions[selectedMenuItem]) {
case ACT_DISK1:
if (((AppleVM *)g_vm)->DiskName(0)[0] != '\0') {
((AppleVM *)g_vm)->ejectDisk(0);
localRedraw = true;
break;
} else {
strcpy(fileFilter, "dsk,.po,nib,woz");
fileSelectionFor = ACT_DISK1;
return BIOS_SELECTFILE;
}
break;
case ACT_DISK2:
if (((AppleVM *)g_vm)->DiskName(1)[0] != '\0') {
((AppleVM *)g_vm)->ejectDisk(1);
localRedraw = true;
break;
} else {
strcpy(fileFilter, "dsk,.po,nib,woz");
fileSelectionFor = ACT_DISK2;
return BIOS_SELECTFILE;
}
break;
case ACT_HD1:
if (((AppleVM *)g_vm)->HDName(0)[0] != '\0') {
((AppleVM *)g_vm)->ejectHD(0);
localRedraw = true;
break;
} else {
strcpy(fileFilter, "img");
fileSelectionFor = ACT_HD1;
return BIOS_SELECTFILE;
}
break;
case ACT_HD2:
if (((AppleVM *)g_vm)->HDName(1)[0] != '\0') {
((AppleVM *)g_vm)->ejectHD(1);
localRedraw = true;
break;
} else {
strcpy(fileFilter, "img");
fileSelectionFor = ACT_HD2;
return BIOS_SELECTFILE;
}
break;
}
}
}
return BIOS_DISKS;
};
uint16_t BIOS::AboutScreenHandler(bool needsRedraw, bool performAction)
{
static bool localRedraw = true;
selectedMenuItem = 0;
if (needsRedraw || localRedraw) {
g_display->clrScr();
g_display->drawString(M_SELECTED,
0,
0,
"Aiie! - an Apple //e emulator");
g_display->drawString(M_NORMAL,
15, 20,
"(c) 2017-2020 Jorj Bauer");
g_display->drawString(M_NORMAL,
15, 38,
"https://github.com/JorjBauer/aiie/");
g_display->drawString(M_NORMAL,
0,
200,
"Press return");
g_display->flush();
localRedraw = false;
}
if (performAction) {
return BIOS_AIIE;
}
return BIOS_ABOUT;
}
uint16_t BIOS::PaddlesScreenHandler(bool needsRedraw, bool performAction)
{
static bool localRedraw = true;
selectedMenuItem = 0;
static uint8_t lastPaddleX = g_paddles->paddle0();
static uint8_t lastPaddleY = g_paddles->paddle1();
if (g_paddles->paddle0() != lastPaddleX) {
lastPaddleX = g_paddles->paddle0();
localRedraw = true;
}
if (g_paddles->paddle1() != lastPaddleY) {
lastPaddleY = g_paddles->paddle1();
localRedraw = true;
}
if (needsRedraw || localRedraw) {
char buf[50];
g_display->clrScr();
sprintf(buf, "Paddle X: %d ", lastPaddleX);
g_display->drawString(M_NORMAL, 0, 12, buf);
sprintf(buf, "Paddle Y: %d ", lastPaddleY);
g_display->drawString(M_NORMAL, 0, 42, buf);
g_display->drawString(M_NORMAL, 0, 92, "Press return to exit");
g_display->flush();
localRedraw = false;
}
if (performAction) {
return BIOS_HARDWARE;
}
return BIOS_PADDLES;
}
uint16_t BIOS::SelectFileScreenHandler(bool needsRedraw, bool performAction)
{
if (selectedMenuItem < 0)
selectedMenuItem = BIOS_MAXFILES + 1;
selectedMenuItem %= BIOS_MAXFILES + 2;
static bool localRedraw = true;
static int8_t sel = 0;
static int8_t page = 0;
static uint16_t fileCount = 0;
if (needsRedraw || localRedraw) {
fileCount = DrawDiskNames(page, sel, fileFilter);
localRedraw = false;
}
if (performAction) {
if (sel == 0) {
page--;
if (page < 0) page = 0;
// else sel = BIOS_MAXFILES + 1;
localRedraw = true;
}
else if (sel == BIOS_MAXFILES+1) {
if (fileCount == BIOS_MAXFILES) { // don't let them select
// 'Next' if there were no
// files in the list or if the
// list isn't full
page++;
//sel = 0;
localRedraw = true;
}
} else if (strcmp(fileDirectory[sel-1], "../") == 0) {
// Go up a directory (strip a directory name from rootPath)
stripDirectory();
page = 0;
//sel = 0;
localRedraw = true;
} else if (fileDirectory[sel-1][strlen(fileDirectory[sel-1])-1] == '/') {
// Descend in to the directory. FIXME: file path length?
strcat(rootPath, fileDirectory[sel-1]);
sel = 0;
page = 0;
localRedraw = true;
} else {
selectedFile = sel - 1;
g_display->flush();
return BIOS_DISKS;
}
}
return BIOS_SELECTFILE;
}
/*
bool BIOS::runUntilDone()
{
// Reset the cache
@ -145,129 +626,10 @@ bool BIOS::runUntilDone()
int8_t prevAction = ACT_EXIT;
while (1) {
switch (prevAction = GetAction(prevAction)) {
case ACT_EXIT:
goto done;
case ACT_COLDBOOT:
ColdReboot();
goto done;
case ACT_RESET:
WarmReset();
goto done;
case ACT_MONITOR:
((AppleVM *)g_vm)->Monitor();
goto done;
case ACT_DISPLAYTYPE:
g_displayType++;
g_displayType %= 4; // FIXME: abstract max #
((AppleDisplay*)g_display)->displayTypeChanged();
break;
case ACT_ABOUT:
showAbout();
break;
case ACT_SPEED:
currentCPUSpeedIndex++;
currentCPUSpeedIndex %= 4;
switch (currentCPUSpeedIndex) {
case CPUSPEED_HALF:
g_speed = 1023000/2;
break;
case CPUSPEED_DOUBLE:
g_speed = 1023000*2;
break;
case CPUSPEED_QUAD:
g_speed = 1023000*4;
break;
default:
g_speed = 1023000;
break;
}
break;
case ACT_DEBUG:
g_debugMode++;
g_debugMode %= 9; // FIXME: abstract max #
break;
case ACT_DISK1:
if (((AppleVM *)g_vm)->DiskName(0)[0] != '\0') {
((AppleVM *)g_vm)->ejectDisk(0);
} else {
if (SelectDiskImage("dsk,.po,nib,woz")) {
((AppleVM *)g_vm)->insertDisk(0, staticPathConcat(rootPath, fileDirectory[selectedFile]), false);
goto done;
}
}
break;
case ACT_DISK2:
if (((AppleVM *)g_vm)->DiskName(1)[0] != '\0') {
((AppleVM *)g_vm)->ejectDisk(1);
} else {
if (SelectDiskImage("dsk,.po,nib,woz")) {
((AppleVM *)g_vm)->insertDisk(1, staticPathConcat(rootPath, fileDirectory[selectedFile]), false);
goto done;
}
}
break;
case ACT_HD1:
if (((AppleVM *)g_vm)->HDName(0)[0] != '\0') {
((AppleVM *)g_vm)->ejectHD(0);
} else {
if (SelectDiskImage("img")) {
((AppleVM *)g_vm)->insertHD(0, staticPathConcat(rootPath, fileDirectory[selectedFile]));
goto done;
}
}
break;
case ACT_HD2:
if (((AppleVM *)g_vm)->HDName(1)[0] != '\0') {
((AppleVM *)g_vm)->ejectHD(1);
} else {
if (SelectDiskImage("img")) {
((AppleVM *)g_vm)->insertHD(1, staticPathConcat(rootPath, fileDirectory[selectedFile]));
goto done;
}
}
break;
case ACT_PADX_INV:
g_invertPaddleX = !g_invertPaddleX;
#ifdef TEENSYDUINO
((TeensyPaddles *)g_paddles)->setRev(g_invertPaddleX, g_invertPaddleY);
#endif
break;
case ACT_PADY_INV:
g_invertPaddleY = !g_invertPaddleY;
#ifdef TEENSYDUINO
((TeensyPaddles *)g_paddles)->setRev(g_invertPaddleX, g_invertPaddleY);
#endif
break;
case ACT_PADDLES:
ConfigurePaddles();
break;
case ACT_VOLPLUS:
g_volume ++;
if (g_volume > 15) {
g_volume = 15;
}
break;
case ACT_VOLMINUS:
g_volume--;
if (g_volume < 0) {
g_volume = 0;
}
break;
case ACT_SUSPEND:
g_display->clrScr();
g_display->drawString(M_SELECTED, 80, 100,"Suspending VM...");
g_display->flush();
// CPU is already suspended, so this is safe...
((AppleVM *)g_vm)->Suspend("suspend.vm");
break;
case ACT_RESTORE:
// CPU is already suspended, so this is safe...
g_display->clrScr();
g_display->drawString(M_SELECTED, 80, 100,"Resuming VM...");
g_display->flush();
((AppleVM *)g_vm)->Resume("suspend.vm");
break;
***
// ConfigurePaddles();
***
}
}
@ -280,6 +642,7 @@ bool BIOS::runUntilDone()
// return true if any persistent setting changed that we want to store in eeprom
return true;
}
*/
void BIOS::WarmReset()
{
@ -292,91 +655,6 @@ void BIOS::ColdReboot()
g_cpu->Reset();
}
uint8_t BIOS::GetAction(int8_t selection)
{
while (1) {
DrawMainMenu();
while (!g_keyboard->kbhit()
#ifdef TEENSYDUINO
&&
(resetButtonDebouncer.read() == HIGH)
#endif
) {
#ifdef TEENSYDUINO
runDebouncer();
delay(10);
#else
usleep(100);
#endif
// Wait for either a keypress or the reset button to be pressed
}
#ifdef TEENSYDUINO
if (resetButtonDebouncer.read() == LOW) {
// wait until it's no longer pressed
while (resetButtonDebouncer.read() == LOW)
runDebouncer();
delay(100); // wait long enough for it to debounce
// then return an exit code
return ACT_EXIT;
}
#else
// FIXME: look for F10 or ESC & return ACT_EXIT?
#endif
// selectedMenuItem and selectedMenu can go out of bounds here, and that's okay;
// the current menu (and the menu bar) will re-pin it appropriately...
switch (g_keyboard->read()) {
case PK_DARR:
selectedMenuItem++;
break;
case PK_UARR:
selectedMenuItem--;
break;
case PK_RARR:
selectedMenu++;
break;
case PK_LARR:
selectedMenu--;
break;
case PK_RET:
{
int8_t activeAction = getCurrentMenuAction();
if (activeAction > 0) {
return activeAction;
}
}
break;
}
}
}
int8_t BIOS::getCurrentMenuAction()
{
int8_t ret = -1;
switch (selectedMenu) {
case 0: // Aiie
if (isActionActive(aiieActions[selectedMenuItem]))
return aiieActions[selectedMenuItem];
break;
case 1: // VM
if (isActionActive(vmActions[selectedMenuItem]))
return vmActions[selectedMenuItem];
break;
case 2: // Hardware
if (isActionActive(hardwareActions[selectedMenuItem]))
return hardwareActions[selectedMenuItem];
break;
case 3: // Disks
if (isActionActive(diskActions[selectedMenuItem]))
return diskActions[selectedMenuItem];
break;
}
return ret;
}
bool BIOS::isActionActive(int8_t action)
{
// don't return true for disk events that aren't valid
@ -673,110 +951,6 @@ void BIOS::DrawCurrentMenu()
}
}
void BIOS::DrawMainMenu()
{
g_display->clrScr();
// g_display->drawString(M_NORMAL, 0, 0, "BIOS Configuration");
DrawMenuBar();
DrawCurrentMenu();
g_display->flush();
}
void BIOS::ConfigurePaddles()
{
while (1) {
bool needsUpdate = true;
uint8_t lastPaddleX = g_paddles->paddle0();
uint8_t lastPaddleY = g_paddles->paddle1();
if (g_paddles->paddle0() != lastPaddleX) {
lastPaddleX = g_paddles->paddle0();
needsUpdate = true;
}
if (g_paddles->paddle1() != lastPaddleY) {
lastPaddleY = g_paddles->paddle1();
needsUpdate = true;
}
if (needsUpdate) {
char buf[50];
g_display->clrScr();
sprintf(buf, "Paddle X: %d ", lastPaddleX);
g_display->drawString(M_NORMAL, 0, 12, buf);
sprintf(buf, "Paddle Y: %d ", lastPaddleY);
g_display->drawString(M_NORMAL, 0, 42, buf);
g_display->drawString(M_NORMAL, 0, 92, "Exit with any key");
g_display->flush();
}
if (g_keyboard->kbhit()) {
g_keyboard->read(); // throw out keypress
return;
}
}
}
// return true if the user selects an image
// sets selectedFile (index; -1 = "nope") and fileDirectory[][] (names of up to BIOS_MAXFILES files)
bool BIOS::SelectDiskImage(const char *filter)
{
int8_t sel = 0;
int8_t page = 0;
uint16_t fileCount = 0;
while (1) {
fileCount = DrawDiskNames(page, sel, filter);
while (!g_keyboard->kbhit())
;
switch (g_keyboard->read()) {
case PK_DARR:
sel++;
sel %= BIOS_MAXFILES + 2;
break;
case PK_UARR:
sel--;
if (sel < 0)
sel = BIOS_MAXFILES + 1;
break;
case PK_ESC:
return false;
case PK_RET:
if (sel == 0) {
page--;
if (page < 0) page = 0;
// else sel = BIOS_MAXFILES + 1;
}
else if (sel == BIOS_MAXFILES+1) {
if (fileCount == BIOS_MAXFILES) { // don't let them select 'Next' if there were no files in the list or if the list isn't full
page++;
//sel = 0;
}
} else if (strcmp(fileDirectory[sel-1], "../") == 0) {
// Go up a directory (strip a directory name from rootPath)
stripDirectory();
page = 0;
//sel = 0;
continue;
} else if (fileDirectory[sel-1][strlen(fileDirectory[sel-1])-1] == '/') {
// Descend in to the directory. FIXME: file path length?
strcat(rootPath, fileDirectory[sel-1]);
sel = 0;
page = 0;
continue;
} else {
selectedFile = sel - 1;
g_display->flush();
return true;
}
break;
}
}
/* NOTREACHED */
}
void BIOS::stripDirectory()
{
rootPath[strlen(rootPath)-1] = '\0'; // remove the last character
@ -919,31 +1093,45 @@ uint16_t BIOS::GatherFilenames(uint8_t pageOffset, const char *filter)
}
}
void BIOS::showAbout()
{
g_display->clrScr();
#if 0
***
switch (fileSelectionFor) {
case ACT_DISK1:
if (SelectDiskImage("dsk,.po,nib,woz")) {
((AppleVM *)g_vm)->insertDisk(0, staticPathConcat(rootPath, fileDirectory[selectedFile]), false);
return BIOS_DONE;
...
case ACT_DISK2:
if (SelectDiskImage("dsk,.po,nib,woz")) {
((AppleVM *)g_vm)->insertDisk(1, staticPathConcat(rootPath, fileDirectory[selectedFile]), false);
return BIOS_DONE;
g_display->drawString(M_SELECTED,
0,
0,
"Aiie! - an Apple //e emulator");
...
case ACT_HD1:
***
if (SelectDiskImage("img")) {
((AppleVM *)g_vm)->insertHD(0, staticPathConcat(rootPath, fileDirectory[selectedFile]));
return BIOS_DONE;
}
g_display->drawString(M_NORMAL,
15, 20,
"(c) 2017-2020 Jorj Bauer");
case ACT_HD2:
***
if (SelectDiskImage("img")) {
((AppleVM *)g_vm)->insertHD(1, staticPathConcat(rootPath, fileDirectory[selectedFile]));
return BIOS_DONE;
}
g_display->drawString(M_NORMAL,
15, 38,
"https://github.com/JorjBauer/aiie/");
g_display->drawString(M_NORMAL,
0,
200,
"Press any key");
...
g_display->flush();
/*
int8_t sel = 0;
int8_t page = 0;
uint16_t fileCount = 0;
while (1) {
fileCount = DrawDiskNames(page, sel, filter);
*/
while (!g_keyboard->kbhit())
;
g_keyboard->read(); // throw out the keypress
}
#endif

20
bios.h
View File

@ -15,10 +15,12 @@ class BIOS {
BIOS();
~BIOS();
// return true if a persistent change needs to be stored in EEPROM
bool runUntilDone();
// return true as long as it's still running
bool loop();
private:
uint16_t MainMenuHandler();
void DrawMenuBar();
void DrawCurrentMenu();
void DrawAiieMenu();
@ -26,25 +28,27 @@ class BIOS {
void DrawHardwareMenu();
void DrawDisksMenu();
uint16_t AiieMenuHandler(bool needsRedraw, bool performAction);
uint16_t VmMenuHandler(bool needsRedraw, bool performAction);
uint16_t HardwareMenuHandler(bool needsRedraw, bool performAction);
uint16_t DisksMenuHandler(bool needsRedraw, bool performAction);
uint16_t AboutScreenHandler(bool needsRedraw, bool performAction);
uint16_t PaddlesScreenHandler(bool needsRedraw, bool performAction);
uint16_t SelectFileScreenHandler(bool needsRedraw, bool performAction);
uint8_t GetAction(int8_t prevAction);
bool isActionActive(int8_t action);
void DrawMainMenu();
int8_t getCurrentMenuAction();
void WarmReset();
void ColdReboot();
bool SelectDiskImage(const char *filter);
uint16_t DrawDiskNames(uint8_t page, int8_t selection, const char *filter);
uint16_t GatherFilenames(uint8_t pageOffset, const char *filter);
void ConfigurePaddles();
void stripDirectory();
void showAbout();
uint16_t cacheAllEntries(const char *filter);
void sortCachedEntries();
void swapCacheEntries(int a, int b);

View File

@ -20,22 +20,14 @@
#include "timeutil.h"
//#define SHOWFPS
//#define SHOWPC
//#define SHOWMEMPAGE
BIOS bios;
Debugger debugger;
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";
@ -91,106 +83,275 @@ 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;
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 0
int policy;
struct sched_param param;
pthread_getschedparam(pthread_self(), &policy, &param);
param.sched_priority = sched_get_priority_max(policy);
pthread_setschedparam(pthread_self(), policy, &param);
#endif
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!
}
return diff;
}
static struct timespec runCPU(struct timespec now)
{
static bool initialized = false;
static struct timespec startTime;
static struct timespec nextInstructionTime;
if (!initialized) {
do_gettime(&startTime);
do_gettime(&nextInstructionTime);
initialized = 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;
if (debugger.active()) {
// With the debugger running, we need to single-step through
// instructions.
executed = g_cpu->Run(1);
} else {
// Otherwise we can run a bunch of instructions at once to
// save on the overhead.
executed = g_cpu->Run(24);
}
// 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();
// FIXME need to reset starttime for this and g_cpu->cycles
}
if (send_rst) {
cpuDebuggerRunning = true;
_init_darwin_shim();
do_gettime(&startTime);
printf("Start time: %lu,%lu\n", startTime.tv_sec, startTime.tv_nsec);
do_gettime(&nextInstructionTime);
printf("Sending reset\n");
g_cpu->Reset();
send_rst = 0;
}
return diff;
}
printf("free-running\n");
#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;
// In this loop, we determine when the next CPU event is; sleep until
// that event; and then perform the event. There are also peripheral
// maintenance calls embedded in the loop...
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
while (1) {
if (g_biosInterrupt) {
printf("BIOS blocking\n");
while (g_biosInterrupt) {
usleep(100);
}
printf("BIOS block complete\n");
// 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;
}
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");
void doDebugging()
{
char buf[25];
static time_t startAt = time(NULL);
static uint32_t loopCount = 0;
wantResume = false;
}
do_gettime(&currentTime);
// Determine the next CPU runtime (nextInstructionTime)
timespec_add_cycles(&startTime, g_cpu->cycles, &nextInstructionTime);
// Sleep until the CPU is ready to run.
// tsSubtract doesn't return negatives; it bounds at zero. So if
// either result is zero then it's time to run something.
struct timespec cpudiff = tsSubtract(nextInstructionTime, currentTime);
if (cpudiff.tv_sec > 0 || cpudiff.tv_nsec > 0) {
// Sleep until the it's ready and loop...
nanosleep(&cpudiff, NULL);
continue;
}
if (cpudiff.tv_sec == 0 && cpudiff.tv_nsec == 0) {
// Run the CPU; it's caught up to "real time"
uint8_t executed = 0;
if (debugger.active()) {
// With the debugger running, we need to single-step through
// instructions.
executed = g_cpu->Run(1);
} else {
// Otherwise we can run a bunch of instructions at once to
// save on the overhead.
executed = g_cpu->Run(24);
}
// 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;
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, 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 (!g_biosInterrupt) {
// If the BIOS is running, then let it handle the keyboard directly
g_keyboard->maintainKeyboard();
}
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();
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();
@ -250,177 +411,11 @@ int main(int argc, char *argv[])
signal(SIGINT, sigint_handler);
signal(SIGPIPE, SIG_IGN); // debugger might have a SIGPIPE happen if the remote end drops
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);
}
g_speaker->begin();
int64_t lastCycleCount = -1;
printf("Starting loop\n");
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);
// FIXME: drain whatever's in the speaker queue
/* 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 int64_t usleepcycles = 16384*4; // step-down for display drawing. Dynamically updated based on FPS calculations.
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_ui->blit();
g_printer->update();
g_keyboard->maintainKeyboard();
doDebugging();
g_ui->drawPercentageUIElement(UIePowerPercentage, 100);
// 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 > 30 && usleepcycles < 0x3FFFFFFF) {
usleepcycles *= 2;
} else if (fps < 20 && usleepcycles > 0xF) {
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
if (g_cpu->cycles == lastCycleCount) {
// If the CPU didn't advance during our last loop, then delay
// here; there can't be any substantial updates, so no need to
// beat up the host machine
usleep(100000);
} else {
lastCycleCount = g_cpu->cycles;
}
}
}
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;*/
loop();
}
}

View File

@ -169,46 +169,52 @@ uint8_t keyPending;
bool SDLKeyboard::kbhit()
{
SDL_Event event;
if (SDL_PollEvent( &event ) &&
event.type == SDL_KEYDOWN) {
SDL_KeyboardEvent *key = &event.key;
if ( (key->keysym.sym >= 'a' && key->keysym.sym <= 'z') ||
(key->keysym.sym >= '0' && key->keysym.sym <= '9') ||
key->keysym.sym == '-' ||
key->keysym.sym == '=' ||
key->keysym.sym == '[' ||
key->keysym.sym == '`' ||
key->keysym.sym == ']' ||
key->keysym.sym == '\\' ||
key->keysym.sym == ';' ||
key->keysym.sym == '\'' ||
key->keysym.sym == ',' ||
key->keysym.sym == '.' ||
key->keysym.sym == '/' ||
key->keysym.sym == ' ' ||
key->keysym.sym == 27 || // ESC
key->keysym.sym == 13 || // return
key->keysym.sym == 9) { // tab
keyPending = key->keysym.sym;
hasKeyPending = true;
} else {
switch (key->keysym.sym) {
case SDLK_UP:
keyPending = PK_UARR;
if (SDL_PollEvent( &event )) {
if (event.type == SDL_QUIT) {
exit(0);
}
if (event.type == SDL_KEYDOWN) {
SDL_KeyboardEvent *key = &event.key;
if ( (key->keysym.sym >= 'a' && key->keysym.sym <= 'z') ||
(key->keysym.sym >= '0' && key->keysym.sym <= '9') ||
key->keysym.sym == '-' ||
key->keysym.sym == '=' ||
key->keysym.sym == '[' ||
key->keysym.sym == '`' ||
key->keysym.sym == ']' ||
key->keysym.sym == '\\' ||
key->keysym.sym == ';' ||
key->keysym.sym == '\'' ||
key->keysym.sym == ',' ||
key->keysym.sym == '.' ||
key->keysym.sym == '/' ||
key->keysym.sym == ' ' ||
key->keysym.sym == 27 || // ESC
key->keysym.sym == 13 || // return
key->keysym.sym == 9) { // tab
keyPending = key->keysym.sym;
hasKeyPending = true;
break;
case SDLK_DOWN:
keyPending = PK_DARR;
hasKeyPending = true;
break;
case SDLK_RIGHT:
keyPending = PK_RARR;
hasKeyPending = true;
break;
case SDLK_LEFT:
keyPending = PK_LARR;
hasKeyPending = true;
break;
} else {
switch (key->keysym.sym) {
case SDLK_UP:
keyPending = PK_UARR;
hasKeyPending = true;
break;
case SDLK_DOWN:
keyPending = PK_DARR;
hasKeyPending = true;
break;
case SDLK_RIGHT:
keyPending = PK_RARR;
hasKeyPending = true;
break;
case SDLK_LEFT:
keyPending = PK_LARR;
hasKeyPending = true;
break;
}
}
}
}

View File

@ -60,6 +60,7 @@ static void audioCallback(void *unused, Uint8 *stream, int len)
} else if (audioRunning==1) {
// waiting for first fill; return an empty buffer.
memset(stream, 0, SDLSIZE*SAMPLEBYTES);
pthread_mutex_unlock(&togmutex);
return;
}