From 538898d371dc2f6ca11dd40faab74ad4050da453 Mon Sep 17 00:00:00 2001 From: Jorj Bauer Date: Mon, 28 Dec 2020 14:23:47 -0500 Subject: [PATCH] bios rework in SDL base --- bios.cpp | 858 ++++++++++++++++++++++++++----------------- bios.h | 20 +- sdl/aiie.cpp | 515 +++++++++++++------------- sdl/sdl-keyboard.cpp | 84 +++-- sdl/sdl-speaker.cpp | 1 + 5 files changed, 836 insertions(+), 642 deletions(-) diff --git a/bios.cpp b/bios.cpp index b780d37..15ee48c 100644 --- a/bios.cpp +++ b/bios.cpp @@ -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 + diff --git a/bios.h b/bios.h index 2c037cb..0f9ab8a 100644 --- a/bios.h +++ b/bios.h @@ -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); diff --git a/sdl/aiie.cpp b/sdl/aiie.cpp index fb4d4d0..8c6d0ce 100644 --- a/sdl/aiie.cpp +++ b/sdl/aiie.cpp @@ -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, ¶m); - param.sched_priority = sched_get_priority_max(policy); - pthread_setschedparam(pthread_self(), policy, ¶m); -#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(¤tTime); - - // 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(); } } diff --git a/sdl/sdl-keyboard.cpp b/sdl/sdl-keyboard.cpp index 12c07ec..89b0b1a 100644 --- a/sdl/sdl-keyboard.cpp +++ b/sdl/sdl-keyboard.cpp @@ -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; + } } } } diff --git a/sdl/sdl-speaker.cpp b/sdl/sdl-speaker.cpp index 9e4ae05..4e1c292 100644 --- a/sdl/sdl-speaker.cpp +++ b/sdl/sdl-speaker.cpp @@ -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; }