diff --git a/LRingBuffer.cpp b/LRingBuffer.cpp new file mode 100644 index 0000000..e8e3a3e --- /dev/null +++ b/LRingBuffer.cpp @@ -0,0 +1,177 @@ +#include "LRingBuffer.h" +#include +#include "globals.h" + +#define RINGBUFFERMAGIC '0' + +LRingBuffer::LRingBuffer(int16_t length) +{ + this->buffer = (uint8_t *)malloc(length); + this->max = length; + this->fill = 0; + this->ptr = 0; + this->cursor = 0; +} + +LRingBuffer::~LRingBuffer() +{ + free (this->buffer); +} + +bool LRingBuffer::Serialize(int8_t fd) +{ + g_filemanager->writeByte(fd, RINGBUFFERMAGIC); + + g_filemanager->writeByte(fd, (max >> 8) & 0xFF); + g_filemanager->writeByte(fd, (max ) & 0xFF); + + g_filemanager->writeByte(fd, (ptr >> 8) & 0xFF); + g_filemanager->writeByte(fd, (ptr ) & 0xFF); + + g_filemanager->writeByte(fd, (fill >> 8) & 0xFF); + g_filemanager->writeByte(fd, (fill ) & 0xFF); + + g_filemanager->writeByte(fd, (cursor >> 8) & 0xFF); + g_filemanager->writeByte(fd, (cursor ) & 0xFF); + + for (uint16_t i=0; iwriteByte(fd, buffer[i]); + } + + g_filemanager->writeByte(fd, RINGBUFFERMAGIC); + + return true; +} + +bool LRingBuffer::Deserialize(int8_t fd) +{ + if (g_filemanager->readByte(fd) != RINGBUFFERMAGIC) + return false; + + max = g_filemanager->readByte(fd); + max <<= 8; + max |= g_filemanager->readByte(fd); + + ptr = g_filemanager->readByte(fd); + ptr <<= 8; + ptr |= g_filemanager->readByte(fd); + + fill = g_filemanager->readByte(fd); + fill <<= 8; + fill |= g_filemanager->readByte(fd); + + cursor = g_filemanager->readByte(fd); + cursor <<= 8; + cursor |= g_filemanager->readByte(fd); + + if (buffer) + free(buffer); + + buffer = (uint8_t *)malloc(max); + + for (uint16_t i=0; ireadByte(fd); + } + + if (g_filemanager->readByte(fd) != RINGBUFFERMAGIC) + return false; + + return true; +} + +void LRingBuffer::clear() +{ + this->fill = 0; +} + +bool LRingBuffer::isFull() +{ + return (this->max == this->fill); +} + +bool LRingBuffer::hasData() +{ + return (this->fill != 0); +} + +bool LRingBuffer::addByte(uint8_t b) +{ + if (this->max == this->fill) + return false; + + int idx = (this->ptr + this->fill) % this->max; + this->buffer[idx] = b; + this->fill++; + return true; +} + +bool LRingBuffer::replaceByte(uint8_t b) +{ + if (cursor < fill) { + buffer[cursor] = b; + cursor++; + if (cursor >= fill) { + cursor = 0; + } + return true; + } + return false; +} + + +bool LRingBuffer::addBytes(uint8_t *b, int count) +{ + for (int i=0; ifill == 0) + return 0; + + uint8_t ret = this->buffer[this->ptr]; + this->fill--; + this->ptr++; + this->ptr %= this->max; + return ret; +} + +uint8_t LRingBuffer::peek(int16_t idx) +{ + uint16_t p = (this->ptr + idx) % this->max; + return this->buffer[p]; +} + +int16_t LRingBuffer::count() +{ + return this->fill; +} + +uint16_t LRingBuffer::getPeekCursor() +{ + return this->cursor; +} + +void LRingBuffer::setPeekCursor(int16_t idx) +{ + this->cursor = idx; +} + +void LRingBuffer::resetPeekCursor() +{ + this->cursor = 0; +} + +uint8_t LRingBuffer::peekNext() +{ + uint8_t ret = peek(cursor); + cursor++; + if (cursor >= fill) { + cursor = 0; + } + return ret; +} diff --git a/RingBuffer.h b/LRingBuffer.h similarity index 72% rename from RingBuffer.h rename to LRingBuffer.h index 90b3bff..cf5e6b3 100644 --- a/RingBuffer.h +++ b/LRingBuffer.h @@ -1,12 +1,15 @@ -#ifndef __RINGBUFFER_H -#define __RINGBUFFER_H +#ifndef __LRINGBUFFER_H +#define __LRINGBUFFER_H #include -class RingBuffer { +class LRingBuffer { public: - RingBuffer(int16_t length); - ~RingBuffer(); + LRingBuffer(int16_t length); + ~LRingBuffer(); + + bool Serialize(int8_t fd); + bool Deserialize(int8_t fd); void clear(); diff --git a/Makefile b/Makefile index 20068da..838cc3b 100755 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ CXXFLAGS=-Wall -I .. -I . -I apple -I sdl -I/usr/local/include/SDL2 -O3 -g TSRC=cpu.cpp util/testharness.cpp -COMMONOBJS=cpu.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o RingBuffer.o globals.o apple/parallelcard.o apple/fx80.o lcg.o apple/hd32.o +COMMONOBJS=cpu.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o LRingBuffer.o globals.o apple/parallelcard.o apple/fx80.o lcg.o apple/hd32.o SDLOBJS=sdl/sdl-speaker.o sdl/sdl-display.o sdl/sdl-keyboard.o sdl/sdl-paddles.o sdl/sdl-filemanager.o sdl/aiie.o sdl/sdl-printer.o sdl/sdl-clock.o diff --git a/RingBuffer.cpp b/RingBuffer.cpp deleted file mode 100644 index 764db0f..0000000 --- a/RingBuffer.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "RingBuffer.h" -#include - -RingBuffer::RingBuffer(int16_t length) -{ - this->buffer = (uint8_t *)malloc(length); - this->max = length; - this->fill = 0; - this->ptr = 0; - this->cursor = 0; -} - -RingBuffer::~RingBuffer() -{ - free (this->buffer); -} - -void RingBuffer::clear() -{ - this->fill = 0; -} - -bool RingBuffer::isFull() -{ - return (this->max == this->fill); -} - -bool RingBuffer::hasData() -{ - return (this->fill != 0); -} - -bool RingBuffer::addByte(uint8_t b) -{ - if (this->max == this->fill) - return false; - - int idx = (this->ptr + this->fill) % this->max; - this->buffer[idx] = b; - this->fill++; - return true; -} - -bool RingBuffer::replaceByte(uint8_t b) -{ - if (cursor < fill) { - buffer[cursor] = b; - cursor++; - if (cursor >= fill) { - cursor = 0; - } - return true; - } - return false; -} - - -bool RingBuffer::addBytes(uint8_t *b, int count) -{ - for (int i=0; ifill == 0) - return 0; - - uint8_t ret = this->buffer[this->ptr]; - this->fill--; - this->ptr++; - this->ptr %= this->max; - return ret; -} - -uint8_t RingBuffer::peek(int16_t idx) -{ - uint16_t p = (this->ptr + idx) % this->max; - return this->buffer[p]; -} - -int16_t RingBuffer::count() -{ - return this->fill; -} - -uint16_t RingBuffer::getPeekCursor() -{ - return this->cursor; -} - -void RingBuffer::setPeekCursor(int16_t idx) -{ - this->cursor = idx; -} - -void RingBuffer::resetPeekCursor() -{ - this->cursor = 0; -} - -uint8_t RingBuffer::peekNext() -{ - uint8_t ret = peek(cursor); - cursor++; - if (cursor >= fill) { - cursor = 0; - } - return ret; -} diff --git a/apple/applemmu.cpp b/apple/applemmu.cpp index 3be2ee3..ae4f3c7 100644 --- a/apple/applemmu.cpp +++ b/apple/applemmu.cpp @@ -13,6 +13,9 @@ #include "globals.h" +// Serializing token for MMU data +#define MMUMAGIC 'M' + // apple //e memory map /* @@ -57,6 +60,113 @@ AppleMMU::~AppleMMU() // FIXME: clean up the memory we allocated } +bool AppleMMU::Serialize(int8_t fd) +{ + g_filemanager->writeByte(fd, MMUMAGIC); + + g_filemanager->writeByte(fd, (switches >> 8) & 0xFF); + g_filemanager->writeByte(fd, (switches ) & 0xFF); + + g_filemanager->writeByte(fd, auxRamRead ? 1 : 0); + g_filemanager->writeByte(fd, auxRamWrite ? 1 : 0); + g_filemanager->writeByte(fd, bank2 ? 1 : 0); + g_filemanager->writeByte(fd, readbsr ? 1 : 0); + g_filemanager->writeByte(fd, writebsr ? 1 : 0); + g_filemanager->writeByte(fd, altzp ? 1 : 0); + g_filemanager->writeByte(fd, intcxrom ? 1 : 0); + g_filemanager->writeByte(fd, slot3rom ? 1 : 0); + g_filemanager->writeByte(fd, slotLatch); + g_filemanager->writeByte(fd, preWriteFlag ? 1 : 0); + g_filemanager->writeByte(fd, anyKeyDown ? 1 : 0); + g_filemanager->writeByte(fd, keyboardStrobe); + + for (uint16_t i=0; i<0x100; i++) { + for (uint8_t j=0; j<5; j++) { + g_filemanager->writeByte(fd, MMUMAGIC); + if (ramPages[i][j]) { + g_filemanager->writeByte(fd, 1); + for (uint16_t k=0; k<0x100; k++) { + g_filemanager->writeByte(fd, ramPages[i][j][k]); + } + } else { + g_filemanager->writeByte(fd, 0); + } + } + } + + // readPages & writePages don't need suspending, but we will need to + // recalculate after resume + + // Not suspending/resuming slots b/c they're a fixed configuration + // in this project. + + g_filemanager->writeByte(fd, MMUMAGIC); + return true; +} + +bool AppleMMU::Deserialize(int8_t fd) +{ + if (g_filemanager->readByte(fd) != MMUMAGIC) { + return false; + } + + switches = g_filemanager->readByte(fd); + switches <<= 8; + switches |= g_filemanager->readByte(fd); + + auxRamRead = g_filemanager->readByte(fd); + auxRamWrite = g_filemanager->readByte(fd); + bank2 = g_filemanager->readByte(fd); + readbsr = g_filemanager->readByte(fd); + writebsr = g_filemanager->readByte(fd); + altzp = g_filemanager->readByte(fd); + intcxrom = g_filemanager->readByte(fd); + slot3rom = g_filemanager->readByte(fd); + slotLatch = g_filemanager->readByte(fd); + preWriteFlag = g_filemanager->readByte(fd); + anyKeyDown = g_filemanager->readByte(fd); + keyboardStrobe = g_filemanager->readByte(fd); + + for (uint16_t i=0; i<0x100; i++) { + for (uint8_t j=0; j<5; j++) { + if (g_filemanager->readByte(fd) != MMUMAGIC) { +#ifndef TEENSYDUINO + printf("Page %d/%d bad magic\n", i, j); +#endif + return false; + } + + if (g_filemanager->readByte(fd)) { + // This page has data +#ifndef TEENSYDUINO + if (!ramPages[i][j]) { + printf("ERROR: shouldn't be writing to this page\n"); + exit(1); + } +#endif + for (uint16_t k=0; k<0x100; k++) { + ramPages[i][j][k] = g_filemanager->readByte(fd); + } + } else { +#ifndef TEENSYDUINO + if (ramPages[i][j]) { + printf("ERROR: this page exists but wasn't serialized?\n"); + exit(1); + } +#endif + } + } + } + + if (g_filemanager->readByte(fd) != MMUMAGIC) + return false; + + // Reset readPages[] and writePages[] and the display + resetDisplay(); + + return true; +} + void AppleMMU::Reset() { resetRAM(); @@ -680,7 +790,7 @@ void AppleMMU::resetRAM() preWriteFlag = false; // Clear all the pages - for (uint8_t i=0; i<0xFF; i++) { + for (uint16_t i=0; i<=0xFF; i++) { for (uint8_t j=0; j<5; j++) { if (ramPages[i][j]) { for (uint16_t k=0; k<0x100; k++) { @@ -741,6 +851,10 @@ void AppleMMU::resetRAM() void AppleMMU::setSlot(int8_t slotnum, Slot *peripheral) { + if (slots[slotnum]) { + delete slots[slotnum]; + } + slots[slotnum] = peripheral; if (slots[slotnum]) { slots[slotnum]->loadROM(ramPages[0xC0 + slotnum][0]); diff --git a/apple/applemmu.h b/apple/applemmu.h index 9c63ebb..f9f940e 100644 --- a/apple/applemmu.h +++ b/apple/applemmu.h @@ -35,6 +35,9 @@ class AppleMMU : public MMU { AppleMMU(AppleDisplay *display); virtual ~AppleMMU(); + virtual bool Serialize(int8_t fd); + virtual bool Deserialize(int8_t fd); + virtual uint8_t read(uint16_t address); virtual uint8_t readDirect(uint16_t address, uint8_t fromPage); virtual void write(uint16_t address, uint8_t v); diff --git a/apple/applevm.cpp b/apple/applevm.cpp index 622401d..71113af 100644 --- a/apple/applevm.cpp +++ b/apple/applevm.cpp @@ -8,6 +8,9 @@ #include "globals.h" +#include +const char *suspendHdr = "Sus1"; + AppleVM::AppleVM() { // FIXME: all this typecasting makes me knife-stabby @@ -46,6 +49,81 @@ AppleVM::~AppleVM() delete parallel; } +void AppleVM::Suspend(const char *fn) +{ + /* Open a new suspend file via the file manager; tell all our + objects to serialize in to it; close the file */ + + int8_t fh = g_filemanager->openFile(fn); + if (fh == -1) { + // Unable to open; skip suspend + return; + } + + /* Header */ + for (int i=0; iwriteByte(fh, suspendHdr[i]); + } + + /* Tell all of the peripherals to suspend */ + if (g_cpu->Serialize(fh) && + disk6->Serialize(fh) && + hd32->Serialize(fh) + ) { +#ifndef TEENSYDUINO + printf("All serialized successfully\n"); +#else + Serial.println("All serialized successfully"); +#endif + } + + g_filemanager->closeFile(fh); +} + +void AppleVM::Resume(const char *fn) +{ + /* Open the given suspend file via the file manager; tell all our + objects to deserialize from it; close the file */ + + int8_t fh = g_filemanager->openFile(fn); + if (fh == -1) { + // Unable to open; skip resume +#ifdef TEENSYDUINO + Serial.print("Unable to open resume file "); + Serial.println(fn); +#else + printf("Unable to open resume file\n"); +#endif + return; + } + + /* Header */ + for (int i=0; ireadByte(fh) != suspendHdr[i]) { + /* Failed to read correct header; abort */ + return; + } + } + + /* Tell all of the peripherals to resume */ + if (g_cpu->Deserialize(fh) && + disk6->Deserialize(fh) && + hd32->Deserialize(fh) + ) { +#ifndef TEENSYDUINO + printf("All deserialized successfully\n"); +#endif + } else { +#ifndef TEENSYDUINO + printf("Deserialization failed\n"); + exit(1); +#endif + } + + g_filemanager->closeFile(fh); + +} + // fixme: make member vars unsigned long paddleCycleTrigger[2] = {0, 0}; diff --git a/apple/applevm.h b/apple/applevm.h index 3ace7dc..904d0f3 100644 --- a/apple/applevm.h +++ b/apple/applevm.h @@ -19,6 +19,9 @@ class AppleVM : public VM { AppleVM(); virtual ~AppleVM(); + void Suspend(const char *fn); + void Resume(const char *fn); + void cpuMaintenance(uint32_t cycles); virtual void Reset(); diff --git a/apple/diskii.cpp b/apple/diskii.cpp index dbaf83f..267d4f3 100644 --- a/apple/diskii.cpp +++ b/apple/diskii.cpp @@ -16,9 +16,11 @@ #include "diskii-rom.h" +#define DISKIIMAGIC 0xAA + DiskII::DiskII(AppleMMU *mmu) { - this->trackBuffer = new RingBuffer(NIBTRACKSIZE); + this->trackBuffer = new LRingBuffer(NIBTRACKSIZE); this->rawTrackBuffer = (uint8_t *)malloc(4096); this->mmu = mmu; @@ -46,6 +48,101 @@ DiskII::~DiskII() free(this->rawTrackBuffer); this->rawTrackBuffer = NULL; } +bool DiskII::Serialize(int8_t fd) +{ + /* Make sure to flush anything to disk first */ + checkFlush(curHalfTrack[selectedDisk]>>1); + if (trackToFlush != -1) { + flushTrack(trackToFlush, diskToFlush); + } + trackToFlush = -1; + + g_filemanager->writeByte(fd, DISKIIMAGIC); + + g_filemanager->writeByte(fd, curHalfTrack[0]); + g_filemanager->writeByte(fd, curHalfTrack[1]); + + g_filemanager->writeByte(fd, curPhase[0]); + g_filemanager->writeByte(fd, curPhase[1]); + + g_filemanager->writeByte(fd, readWriteLatch); + g_filemanager->writeByte(fd, writeMode); + g_filemanager->writeByte(fd, writeProt); + + // Don't save disk[0,1]; save their file names & cursors + g_filemanager->SerializeFile(fd, disk[0]); + g_filemanager->SerializeFile(fd, disk[1]); + + g_filemanager->writeByte(fd, indicatorIsOn[0]); + g_filemanager->writeByte(fd, indicatorIsOn[1]); + + g_filemanager->writeByte(fd, diskType[0]); + g_filemanager->writeByte(fd, diskType[1]); + g_filemanager->writeByte(fd, selectedDisk); + + trackBuffer->Serialize(fd); + + // FIXME: don't know if we need these + // trackDirty - should always be unset, since we just flushed + // rawTrackBuffer - only used when reading an image + // trackToRead - should be able to leave this unset & reread + // trackToFlush - just flushed + // diskToFlush - just flushed + + g_filemanager->writeByte(fd, DISKIIMAGIC); + + return true; +} + +bool DiskII::Deserialize(int8_t fd) +{ + /* Make sure to flush anything to disk first */ + checkFlush(curHalfTrack[selectedDisk]>>1); + if (trackToFlush != -1) { + flushTrack(trackToFlush, diskToFlush); + } + trackToFlush = -1; + + if (g_filemanager->readByte(fd) != DISKIIMAGIC) { + return false; + } + + curHalfTrack[0] = g_filemanager->readByte(fd); + curHalfTrack[1] = g_filemanager->readByte(fd); + + curPhase[0] = g_filemanager->readByte(fd); + curPhase[1] = g_filemanager->readByte(fd); + + readWriteLatch = g_filemanager->readByte(fd); + writeMode = g_filemanager->readByte(fd); + writeProt = g_filemanager->readByte(fd); + + disk[0] = g_filemanager->DeserializeFile(fd); + disk[1] = g_filemanager->DeserializeFile(fd); + + indicatorIsOn[0] = g_filemanager->readByte(fd); + indicatorIsOn[1] = g_filemanager->readByte(fd); + + diskType[0] = g_filemanager->readByte(fd); + diskType[1] = g_filemanager->readByte(fd); + + selectedDisk = g_filemanager->readByte(fd); + + trackBuffer->Deserialize(fd); + + // Reset the dirty caches and whatnot + trackDirty = -1; + trackToRead = -1; + trackToFlush = -1; + diskToFlush = -1; + + if (g_filemanager->readByte(fd) != DISKIIMAGIC) { + return false; + } + + return true; +} + void DiskII::Reset() { curPhase[0] = curPhase[1] = 0; diff --git a/apple/diskii.h b/apple/diskii.h index f002c8f..28d64c7 100644 --- a/apple/diskii.h +++ b/apple/diskii.h @@ -12,7 +12,7 @@ #include "applemmu.h" #include "Slot.h" -#include "RingBuffer.h" +#include "LRingBuffer.h" #include "nibutil.h" class DiskII : public Slot { @@ -20,6 +20,9 @@ class DiskII : public Slot { DiskII(AppleMMU *mmu); virtual ~DiskII(); + virtual bool Serialize(int8_t fd); + virtual bool Deserialize(int8_t fd); + virtual void Reset(); // used by BIOS cold-boot virtual uint8_t readSwitches(uint8_t s); virtual void writeSwitches(uint8_t s, uint8_t v); @@ -52,7 +55,7 @@ class DiskII : public Slot { volatile int8_t curPhase[2]; volatile bool trackDirty; // does this track need flushing to disk? uint8_t readWriteLatch; - RingBuffer *trackBuffer; // nibblized data + LRingBuffer *trackBuffer; // nibblized data uint8_t *rawTrackBuffer; // not nibblized data bool writeMode; diff --git a/apple/hd32.cpp b/apple/hd32.cpp index 515865c..8ff4e65 100644 --- a/apple/hd32.cpp +++ b/apple/hd32.cpp @@ -58,6 +58,16 @@ HD32::~HD32() { } +bool HD32::Serialize(int8_t fd) +{ + return true; +} + +bool HD32::Deserialize(int8_t fd) +{ + return true; +} + void HD32::Reset() { enabled = 1; diff --git a/apple/hd32.h b/apple/hd32.h index 8b7e808..39ee4fa 100644 --- a/apple/hd32.h +++ b/apple/hd32.h @@ -12,13 +12,16 @@ #include "applemmu.h" #include "Slot.h" -#include "RingBuffer.h" +#include "LRingBuffer.h" class HD32 : public Slot { public: HD32(AppleMMU *mmu); virtual ~HD32(); + virtual bool Serialize(int8_t fd); + virtual bool Deserialize(int8_t fd); + virtual void Reset(); // used by BIOS cold-boot virtual uint8_t readSwitches(uint8_t s); virtual void writeSwitches(uint8_t s, uint8_t v); @@ -45,7 +48,7 @@ class HD32 : public Slot { uint8_t enabled; uint8_t errorState[2]; // status of last operation - uint16_t memBlock[2]; // ?? + uint16_t memBlock[2]; // pointer to memory (for writing blocks to disk) uint16_t diskBlock[2]; // currently selected block int8_t fd[2]; diff --git a/apple/nibutil.cpp b/apple/nibutil.cpp index 31fa8cc..9bc4283 100644 --- a/apple/nibutil.cpp +++ b/apple/nibutil.cpp @@ -56,7 +56,7 @@ const static uint8_t _detrans[0x80] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0, 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC }; -void nibblizeTrack(RingBuffer *trackBuffer, uint8_t *rawTrackBuffer, +void nibblizeTrack(LRingBuffer *trackBuffer, uint8_t *rawTrackBuffer, uint8_t diskType, int8_t track) { int checksum; @@ -126,7 +126,7 @@ void nibblizeTrack(RingBuffer *trackBuffer, uint8_t *rawTrackBuffer, // trackBuf. This reads from the circular buffer trackBuffer, so if // there's not enough data there, the results are somewhat // unpredictable. -bool decodeData(RingBuffer *trackBuffer, uint16_t startAt, uint8_t *output) +bool decodeData(LRingBuffer *trackBuffer, uint16_t startAt, uint8_t *output) { // Basic check that there's enough buffer data in trackBuffer. Note // that we're not checking it against startAt; we could be wrapping @@ -178,7 +178,7 @@ bool decodeData(RingBuffer *trackBuffer, uint16_t startAt, uint8_t *output) return true; } -void encodeData(RingBuffer *trackBuffer, uint8_t *data) +void encodeData(LRingBuffer *trackBuffer, uint8_t *data) { int16_t i; int ptr2 = 0; @@ -221,7 +221,7 @@ void encodeData(RingBuffer *trackBuffer, uint8_t *data) trackBuffer->addByte(_trans[lastv]); } -nibErr denibblizeTrack(RingBuffer *trackBuffer, uint8_t *rawTrackBuffer, +nibErr denibblizeTrack(LRingBuffer *trackBuffer, uint8_t *rawTrackBuffer, uint8_t diskType, int8_t track) { // We can't tell exactly what the length should be, b/c there might diff --git a/apple/nibutil.h b/apple/nibutil.h index 64ad9c5..5c7107b 100644 --- a/apple/nibutil.h +++ b/apple/nibutil.h @@ -8,7 +8,7 @@ #include #endif -#include "RingBuffer.h" +#include "LRingBuffer.h" #define NIBTRACKSIZE 0x1A00 // Minimum viable nibblized sector size. With GAP bytes, could be much longer. @@ -32,13 +32,13 @@ enum nibErr { errorMissingSectors = 2 }; -void nibblizeTrack(RingBuffer *trackBuffer, uint8_t *rawTrackBuffer, +void nibblizeTrack(LRingBuffer *trackBuffer, uint8_t *rawTrackBuffer, uint8_t diskType, int8_t track); -nibErr denibblizeTrack(RingBuffer *trackBuffer, uint8_t *rawTrackBuffer, +nibErr denibblizeTrack(LRingBuffer *trackBuffer, uint8_t *rawTrackBuffer, uint8_t diskType, int8_t track); -bool decodeData(RingBuffer *trackBuffer, uint16_t startAt, uint8_t *output); -void encodeData(RingBuffer *trackBuffer, uint8_t *data); +bool decodeData(LRingBuffer *trackBuffer, uint16_t startAt, uint8_t *output); +void encodeData(LRingBuffer *trackBuffer, uint8_t *data); diff --git a/apple/parallelcard.cpp b/apple/parallelcard.cpp index 696b3d4..c20498c 100644 --- a/apple/parallelcard.cpp +++ b/apple/parallelcard.cpp @@ -13,6 +13,16 @@ ParallelCard::~ParallelCard() { } +bool ParallelCard::Serialize(int8_t fd) +{ + return true; +} + +bool ParallelCard::Deserialize(int8_t fd) +{ + return true; +} + void ParallelCard::Reset() { } diff --git a/apple/parallelcard.h b/apple/parallelcard.h index 196ca54..9ff20eb 100644 --- a/apple/parallelcard.h +++ b/apple/parallelcard.h @@ -18,6 +18,9 @@ class ParallelCard : public Slot { ParallelCard(); virtual ~ParallelCard(); + virtual bool Serialize(int8_t fd); + virtual bool Deserialize(int8_t fd); + virtual void Reset(); // used by BIOS cold-boot virtual uint8_t readSwitches(uint8_t s); virtual void writeSwitches(uint8_t s, uint8_t v); diff --git a/apple/slot.h b/apple/slot.h index 792570f..e394e06 100644 --- a/apple/slot.h +++ b/apple/slot.h @@ -13,6 +13,9 @@ class Slot { public: virtual ~Slot() {}; + virtual bool Serialize(int8_t fd) = 0; + virtual bool Deserialize(int8_t fd) = 0; + virtual void Reset() = 0; // for use at cold-boot virtual uint8_t readSwitches(uint8_t s) = 0; diff --git a/cpu.cpp b/cpu.cpp index 6bb49c5..444b96d 100644 --- a/cpu.cpp +++ b/cpu.cpp @@ -1,9 +1,14 @@ +#ifdef TEENSYDUINO +#include +#endif #include "cpu.h" #include #include #include #include "mmu.h" +#include "globals.h" + // To see calls to unimplemented opcodes, define this: //#define VERBOSE_CPU_ERRORS @@ -21,6 +26,9 @@ #define writemem(addr, val) mmu->write(addr, val) #define readmem(addr) mmu->read(addr) +// serialize suspend/restore token +#define CPUMAGIC 0x65 + enum optype { O_ILLEGAL, O_ADC, @@ -412,6 +420,81 @@ Cpu::~Cpu() mmu = NULL; } +bool Cpu::Serialize(int8_t fh) +{ + g_filemanager->writeByte(fh, CPUMAGIC); + + g_filemanager->writeByte(fh, (pc >> 8) & 0xFF); + g_filemanager->writeByte(fh, (pc ) & 0xFF); + + g_filemanager->writeByte(fh, sp); + g_filemanager->writeByte(fh, a); + g_filemanager->writeByte(fh, x); + g_filemanager->writeByte(fh, y); + g_filemanager->writeByte(fh, flags); + + g_filemanager->writeByte(fh, (cycles >> 24) & 0xFF); + g_filemanager->writeByte(fh, (cycles >> 16) & 0xFF); + g_filemanager->writeByte(fh, (cycles >> 8) & 0xFF); + g_filemanager->writeByte(fh, (cycles ) & 0xFF); + + g_filemanager->writeByte(fh, irqPending ? 1 : 0); + + if (!mmu->Serialize(fh)) { +#ifndef TEENSYDUINO + printf("MMU serialization failed\n"); +#else + Serial.println("MMU serialization failed"); +#endif + return false; + } + + g_filemanager->writeByte(fh, CPUMAGIC); + + return true; // FIXME: no error checking on writes +} + +bool Cpu::Deserialize(int8_t fh) +{ + if (g_filemanager->readByte(fh) != CPUMAGIC) + return false; + + pc = g_filemanager->readByte(fh); + pc <<= 8; + pc |= g_filemanager->readByte(fh); + + sp = g_filemanager->readByte(fh); + a = g_filemanager->readByte(fh); + x = g_filemanager->readByte(fh); + y = g_filemanager->readByte(fh); + flags = g_filemanager->readByte(fh); + + cycles = g_filemanager->readByte(fh); + cycles <<= 8; + cycles |= g_filemanager->readByte(fh); + cycles <<= 8; + cycles |= g_filemanager->readByte(fh); + cycles <<= 8; + cycles |= g_filemanager->readByte(fh); + + irqPending = g_filemanager->readByte(fh) ? true : false; + + if (!mmu->Deserialize(fh)) { +#ifndef TEENSYDUINO + printf("MMU deserialization failed\n"); +#endif + return false; + } + + if (g_filemanager->readByte(fh) != CPUMAGIC) + return false; + +#ifndef TEENSYDUINO + printf("CPU deserialization complete\n"); +#endif + return true; +} + void Cpu::Reset() { a = 0; diff --git a/cpu.h b/cpu.h index 29877fb..ba04dcc 100644 --- a/cpu.h +++ b/cpu.h @@ -28,6 +28,9 @@ class Cpu { Cpu(); ~Cpu(); + bool Serialize(int8_t fh); + bool Deserialize(int8_t fh); + void Reset(); void nmi(); diff --git a/filemanager.h b/filemanager.h index 2b9326e..82f42f1 100644 --- a/filemanager.h +++ b/filemanager.h @@ -9,10 +9,90 @@ #define MAXPATH 255 #endif +#define FMMAGIC 'F' + class FileManager { public: virtual ~FileManager() {}; + virtual bool SerializeFile(int8_t outfd, int8_t fd) { + writeByte(outfd, FMMAGIC); + + if (fd == -1 || + cachedNames[fd][0] == '\0') { + + // No file to cache; we're done + writeByte(outfd, 0); + writeByte(outfd, FMMAGIC); + + return true; + } + + // have a file to cache; set a marker and continue + writeByte(outfd, 1); + + int8_t l = 0; + char *p = cachedNames[fd]; + while (*p) { + l++; + p++; + } + + writeByte(outfd, l); + for (int i=0; i> 24) & 0xFF); + writeByte(outfd, (fileSeekPositions[fd] >> 16) & 0xFF); + writeByte(outfd, (fileSeekPositions[fd] >> 8) & 0xFF); + writeByte(outfd, (fileSeekPositions[fd] ) & 0xFF); + + writeByte(outfd, FMMAGIC); + + return true; + } + + virtual int8_t DeserializeFile(int8_t infd) { + if (readByte(infd) != FMMAGIC) + return -1; + + if (readByte(infd) == 0) { + // No file was cached. Verify footer and we're done without error + + if (readByte(infd) != FMMAGIC) { + // FIXME: no way to raise this error. + return -1; + } + + return -1; + } + + char buf[MAXPATH]; + int8_t l = readByte(infd); + for (int i=0; iSuspend("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; + } + // cycle down the CPU... do_gettime(¤tTime); struct timespec diff = tsSubtract(nextInstructionTime, currentTime); @@ -136,6 +154,17 @@ static void *cpu_thread(void *dummyptr) { #endif if (send_rst) { + + +#if 0 + printf("Scheduling suspend request...\n"); + wantSuspend = true; +#endif +#if 1 + printf("Scheduling resume resume request...\n"); + wantResume = true; +#endif + #if 0 printf("Sending reset\n"); g_cpu->Reset(); @@ -145,7 +174,9 @@ static void *cpu_thread(void *dummyptr) { //g_vm->Reset(); //g_cpu->Reset(); //((AppleVM *)g_vm)->insertDisk(0, "disks/DIAGS.DSK"); -#else +#endif + +#if 0 // Swap disks if (disk1name[0] && disk2name[0]) { printf("Swapping disks\n"); @@ -155,8 +186,9 @@ static void *cpu_thread(void *dummyptr) { printf("Inserting disk %s in drive 2\n", disk1name); ((AppleVM *)g_vm)->insertDisk(1, disk1name); } - /* -#else +#endif + +#if 0 MMU *mmu = g_vm->getMMU(); printf("PC: 0x%X\n", g_cpu->pc); @@ -165,7 +197,6 @@ static void *cpu_thread(void *dummyptr) { } printf("\n"); - printf("Dropping to monitor\n"); // drop directly to monitor. g_cpu->pc = 0xff69; // "call -151" diff --git a/sdl/sdl-clock.cpp b/sdl/sdl-clock.cpp index 49f7bfe..e7ae371 100644 --- a/sdl/sdl-clock.cpp +++ b/sdl/sdl-clock.cpp @@ -37,6 +37,16 @@ SDLClock::~SDLClock() { } +bool SDLClock::Serialize(int8_t fd) +{ + return true; +} + +bool SDLClock::Deserialize(int8_t fd) +{ + return true; +} + void SDLClock::Reset() { } diff --git a/sdl/sdl-clock.h b/sdl/sdl-clock.h index 3a803c5..e12bf55 100644 --- a/sdl/sdl-clock.h +++ b/sdl/sdl-clock.h @@ -12,6 +12,9 @@ class SDLClock : public Slot { SDLClock(AppleMMU *mmu); virtual ~SDLClock(); + virtual bool Serialize(int8_t fd); + virtual bool Deserialize(int8_t fd); + virtual void Reset(); virtual uint8_t readSwitches(uint8_t s); diff --git a/sdl/sdl-filemanager.cpp b/sdl/sdl-filemanager.cpp index 0ddde4a..9e22b09 100644 --- a/sdl/sdl-filemanager.cpp +++ b/sdl/sdl-filemanager.cpp @@ -8,7 +8,6 @@ #include "sdl-filemanager.h" - SDLFileManager::SDLFileManager() { numCached = 0; @@ -92,7 +91,7 @@ bool SDLFileManager::readTrack(int8_t fd, uint8_t *toWhere, bool isNib) // open, seek, read, close. bool ret = false; int ffd = open(cachedNames[fd], O_RDONLY); - if (ffd) { + if (ffd != -1) { lseek(ffd, fileSeekPositions[fd], SEEK_SET); if (isNib) { ret = (read(ffd, toWhere, 0x1A00) == 0x1A00); @@ -117,7 +116,7 @@ bool SDLFileManager::readBlock(int8_t fd, uint8_t *toWhere, bool isNib) // open, seek, read, close. bool ret = false; int ffd = open(cachedNames[fd], O_RDONLY); - if (ffd) { + if (ffd != -1) { lseek(ffd, fileSeekPositions[fd], SEEK_SET); if (isNib) { ret = (read(ffd, toWhere, 416) == 416); @@ -146,7 +145,7 @@ bool SDLFileManager::writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib) // open, seek, write, close. int ffd = open(cachedNames[fd], O_WRONLY); - if (ffd) { + if (ffd != -1) { if (lseek(ffd, fileSeekPositions[fd], SEEK_SET) != fileSeekPositions[fd]) { printf("ERROR: failed to seek to %lu\n", fileSeekPositions[fd]); return false; @@ -171,7 +170,7 @@ bool SDLFileManager::writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib) // open, seek, write, close. int ffd = open(cachedNames[fd], O_WRONLY); - if (ffd) { + if (ffd != -1) { if (lseek(ffd, fileSeekPositions[fd], SEEK_SET) != fileSeekPositions[fd]) { printf("ERROR: failed to seek to %lu\n", fileSeekPositions[fd]); return false; @@ -202,7 +201,7 @@ uint8_t SDLFileManager::readByteAt(int8_t fd, uint32_t pos) // open, seek, read, close. bool ret = false; int ffd = open(cachedNames[fd], O_RDONLY); - if (ffd) { + if (ffd != -1) { lseek(ffd, pos, SEEK_SET); ret = (read(ffd, &v, 1) == 1); close(ffd); @@ -226,8 +225,8 @@ bool SDLFileManager::writeByteAt(int8_t fd, uint8_t v, uint32_t pos) // open, seek, write, close. bool ret = false; - int ffd = open(cachedNames[fd], O_WRONLY); - if (ffd) { + int ffd = open(cachedNames[fd], O_WRONLY|O_CREAT, 0644); + if (ffd != -1) { lseek(ffd, pos, SEEK_SET); ret = (write(ffd, &v, 1) == 1); close(ffd); @@ -236,3 +235,61 @@ bool SDLFileManager::writeByteAt(int8_t fd, uint8_t v, uint32_t pos) return ret; } +bool SDLFileManager::writeByte(int8_t fd, uint8_t v) +{ + if (fd < 0 || fd >= numCached) + return false; + + if (cachedNames[fd][0] == 0) + return false; + + uint32_t pos = fileSeekPositions[fd]; + + // open, seek, write, close. + bool ret = false; + int ffd = open(cachedNames[fd], O_WRONLY|O_CREAT, 0644); + if (ffd != -1) { + lseek(ffd, pos, SEEK_SET); + ret = (write(ffd, &v, 1) == 1); + if (!ret) { + printf("error writing: %d\n", errno); + } + close(ffd); + } else { + printf("Failed to open '%s' for writing: %d\n", + cachedNames[fd], errno); + } + fileSeekPositions[fd]++; + return ret; +} + +uint8_t SDLFileManager::readByte(int8_t fd) +{ + if (fd < 0 || fd >= numCached) + return -1; // FIXME: error handling? + + if (cachedNames[fd][0] == 0) + return -1; // FIXME: error handling? + + uint8_t v = 0; + + uint32_t pos = fileSeekPositions[fd]; + + // open, seek, read, close. + bool ret = false; + int ffd = open(cachedNames[fd], O_RDONLY); + if (ffd != -1) { + lseek(ffd, pos, SEEK_SET); + ret = (read(ffd, &v, 1) == 1); + close(ffd); + } + fileSeekPositions[fd]++; + + if (!ret) { + printf("ERROR reading: %d\n", errno); + } + + // FIXME: error handling? + return v; +} + diff --git a/sdl/sdl-filemanager.h b/sdl/sdl-filemanager.h index 7e367ec..690a109 100644 --- a/sdl/sdl-filemanager.h +++ b/sdl/sdl-filemanager.h @@ -8,7 +8,7 @@ class SDLFileManager : public FileManager { public: SDLFileManager(); virtual ~SDLFileManager(); - + virtual int8_t openFile(const char *name); virtual void closeFile(int8_t fd); @@ -24,10 +24,11 @@ class SDLFileManager : public FileManager { virtual uint8_t readByteAt(int8_t fd, uint32_t pos); virtual bool writeByteAt(int8_t fd, uint8_t v, uint32_t pos); + virtual uint8_t readByte(int8_t fd); + virtual bool writeByte(int8_t fd, uint8_t v); + private: int8_t numCached; - char cachedNames[MAXFILES][MAXPATH]; - unsigned long fileSeekPositions[MAXFILES]; }; diff --git a/teensy/LRingBuffer.cpp b/teensy/LRingBuffer.cpp new file mode 120000 index 0000000..a75a50d --- /dev/null +++ b/teensy/LRingBuffer.cpp @@ -0,0 +1 @@ +../LRingBuffer.cpp \ No newline at end of file diff --git a/teensy/LRingBuffer.h b/teensy/LRingBuffer.h new file mode 120000 index 0000000..2bd9ea0 --- /dev/null +++ b/teensy/LRingBuffer.h @@ -0,0 +1 @@ +../LRingBuffer.h \ No newline at end of file diff --git a/teensy/bios.cpp b/teensy/bios.cpp index 316aafb..625ab19 100644 --- a/teensy/bios.cpp +++ b/teensy/bios.cpp @@ -22,11 +22,13 @@ enum { ACT_HD2 = 9, ACT_VOLPLUS = 10, ACT_VOLMINUS = 11, + ACT_SUSPEND = 12, + ACT_RESTORE = 13, - NUM_ACTIONS = 12 + NUM_ACTIONS = 14 }; -const char *titles[NUM_ACTIONS] = { "Resume", +const char *titles[NUM_ACTIONS] = { "Resume VM", "Reset", "Cold Reboot", "Drop to Monitor", @@ -37,7 +39,9 @@ const char *titles[NUM_ACTIONS] = { "Resume", "%s HD 1", "%s HD 2", "Volume +", - "Volume -" + "Volume -", + "Suspend", + "Restore" }; // FIXME: abstract the pin # rather than repeating it here @@ -162,6 +166,15 @@ bool BIOS::runUntilDone() } volumeDidChange = true; break; + + case ACT_SUSPEND: + // 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... + ((AppleVM *)g_vm)->Resume("suspend.vm"); + break; } } @@ -236,6 +249,8 @@ bool BIOS::isActionActive(int8_t action) case ACT_DISK2: case ACT_HD1: case ACT_HD2: + case ACT_SUSPEND: + case ACT_RESTORE: return true; case ACT_VOLPLUS: @@ -313,7 +328,7 @@ void BIOS::DrawMainMenu(int8_t selection) // draw the volume bar uint16_t volCutoff = 300.0 * (float)((float) g_volume / 15.0); - for (uint8_t y=200; y<=210; y++) { + for (uint8_t y=220; y<=230; y++) { ((TeensyDisplay *)g_display)->moveTo(10, y); for (uint16_t x = 0; x< 300; x++) { ((TeensyDisplay *)g_display)->drawNextPixel( x <= volCutoff ? 0xFFFF : 0x0010 ); diff --git a/teensy/teensy-clock.cpp b/teensy/teensy-clock.cpp index f1eb38c..dbf9f4e 100644 --- a/teensy/teensy-clock.cpp +++ b/teensy/teensy-clock.cpp @@ -37,6 +37,16 @@ TeensyClock::~TeensyClock() { } +bool TeensyClock::Serialize(int8_t fd) +{ + return true; +} + +bool TeensyClock::Deserialize(int8_t fd) +{ + return true; +} + void TeensyClock::Reset() { } diff --git a/teensy/teensy-clock.h b/teensy/teensy-clock.h index 489dd9a..630fa3b 100644 --- a/teensy/teensy-clock.h +++ b/teensy/teensy-clock.h @@ -16,6 +16,9 @@ class TeensyClock : public Slot { TeensyClock(AppleMMU *mmu); virtual ~TeensyClock(); + virtual bool Serialize(int8_t fd); + virtual bool Deserialize(int8_t fd); + virtual void Reset(); virtual uint8_t readSwitches(uint8_t s); diff --git a/teensy/teensy-filemanager.cpp b/teensy/teensy-filemanager.cpp index fab7127..4088c18 100644 --- a/teensy/teensy-filemanager.cpp +++ b/teensy/teensy-filemanager.cpp @@ -79,6 +79,8 @@ void TeensyFileManager::closeFile(int8_t fd) { // invalidate the raw file cache if (rawFd != -1) { + Serial.print("Invalidating raw file cache "); + Serial.println(rawFd); f_close(&rawFil); rawFd = -1; } @@ -298,21 +300,26 @@ bool TeensyFileManager::_prepCache(int8_t fd) // Not our cached file, or we have no cached file if (rawFd != -1) { // Close the old one if we had one - Serial.println("closing old HD cache"); + Serial.println("closing old cache file"); f_close(&rawFil); rawFd = -1; } + Serial.println("opening new cache file"); // Open the new one TCHAR buf[MAXPATH]; char2tchar(cachedNames[fd], MAXPATH, buf); - rc = f_open(&rawFil, (TCHAR*) buf, FA_READ|FA_WRITE); + rc = f_open(&rawFil, (TCHAR*) buf, FA_READ|FA_WRITE|FA_OPEN_ALWAYS); if (rc) { - Serial.println("readByteAt: failed to open"); + Serial.print("_prepCache: failed to open "); + Serial.println(cachedNames[fd]); return false; } - Serial.println("new cache file open"); rawFd = fd; // cache is live + Serial.print("New cache file is "); + Serial.println(fd); + } else { + // Serial.println("reopning same cache"); } return (!rc); @@ -364,3 +371,65 @@ bool TeensyFileManager::writeByteAt(int8_t fd, uint8_t v, uint32_t pos) return (ret == 1); } +uint8_t TeensyFileManager::readByte(int8_t fd) +{ + // open, seek, read, close. + if (fd < 0 || fd >= numCached) + return false; + + if (cachedNames[fd][0] == 0) + return false; + + FRESULT rc; + + _prepCache(fd); + + uint32_t pos = fileSeekPositions[fd]; + + rc = f_lseek(&rawFil, pos); + if (rc) { + Serial.println("readByteAt: seek failed"); + return false; + } + uint8_t b; + UINT v; + f_read(&rawFil, &b, 1, &v); + + fileSeekPositions[fd]++; + + // FIXME: check v == 1 & handle error + return b; +} + +bool TeensyFileManager::writeByte(int8_t fd, uint8_t v) +{ + // open, seek, write, close. + if (fd < 0 || fd >= numCached) { + Serial.println("failed writeByte - invalid fd"); + return false; + } + + if (cachedNames[fd][0] == 0) { + Serial.println("failed writeByte - no cache name"); + return false; + } + + FRESULT rc; + + _prepCache(fd); + + uint32_t pos = fileSeekPositions[fd]; + + rc = f_lseek(&rawFil, pos); + UINT ret; + f_write(&rawFil, &v, 1, &ret); + + fileSeekPositions[fd]++; + + if (ret != 1) { + Serial.println("Write failed"); + } + + return (ret == 1); +} + diff --git a/teensy/teensy-filemanager.h b/teensy/teensy-filemanager.h index 2c71196..c0f0a1f 100644 --- a/teensy/teensy-filemanager.h +++ b/teensy/teensy-filemanager.h @@ -8,7 +8,7 @@ class TeensyFileManager : public FileManager { public: TeensyFileManager(); virtual ~TeensyFileManager(); - + virtual int8_t openFile(const char *name); virtual void closeFile(int8_t fd); @@ -23,14 +23,15 @@ class TeensyFileManager : public FileManager { virtual uint8_t readByteAt(int8_t fd, uint32_t pos); virtual bool writeByteAt(int8_t fd, uint8_t v, uint32_t pos); + + virtual uint8_t readByte(int8_t fd); + virtual bool writeByte(int8_t fd, uint8_t v); private: bool _prepCache(int8_t fd); private: int8_t numCached; - char cachedNames[MAXFILES][MAXPATH]; - unsigned long fileSeekPositions[MAXFILES]; }; #endif diff --git a/teensy/teensy-keyboard.cpp b/teensy/teensy-keyboard.cpp index 8766ea4..e6629e9 100644 --- a/teensy/teensy-keyboard.cpp +++ b/teensy/teensy-keyboard.cpp @@ -1,7 +1,7 @@ #include #include "teensy-keyboard.h" #include -#include +#include "LRingBuffer.h" const byte ROWS = 5; const byte COLS = 13; @@ -17,7 +17,7 @@ char keys[ROWS][COLS] = { uint8_t rowsPins[ROWS] = { 33, 34, 35, 36, 37 }; uint8_t colsPins[COLS] = { 0, 1, 3, 4, 24, 25, 26, 27, 28, 29, 30, 31, 32 }; Keypad keypad(makeKeymap(keys), rowsPins, colsPins, ROWS, COLS); -RingBuffer buffer(10); // 10 keys should be plenty, right? +LRingBuffer buffer(10); // 10 keys should be plenty, right? static uint8_t shiftedNumber[] = { '<', // , '_', // - diff --git a/vm.h b/vm.h index d5afe28..186ec58 100644 --- a/vm.h +++ b/vm.h @@ -17,6 +17,9 @@ class VM { VM() { mmu=NULL; vmdisplay = NULL; videoBuffer = (uint8_t *)calloc(DISPLAYRUN * DISPLAYHEIGHT / 2, 1); hasIRQ = false;} virtual ~VM() { if (mmu) delete mmu; if (vmdisplay) delete vmdisplay; free(videoBuffer); } + virtual void Suspend(const char *fn) = 0; + virtual void Resume(const char *fn) = 0; + virtual void SetMMU(MMU *mmu) { this->mmu = mmu; } virtual MMU *getMMU() { return mmu; } virtual VMKeyboard *getKeyboard() = 0;