diff --git a/Makefile b/Makefile index a870d2e..9780a3a 100755 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ CXXFLAGS=-Wall -I/usr/include/SDL2 -I .. -I . -I apple -I nix -I sdl -I/usr/loca 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 LRingBuffer.o globals.o apple/parallelcard.o apple/fx80.o lcg.o apple/hd32.o images.o apple/appleui.o vmram.o bios.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 images.o apple/appleui.o vmram.o bios.o apple/noslotclock.o FBOBJS=linuxfb/linux-speaker.o linuxfb/fb-display.o linuxfb/linux-keyboard.o linuxfb/fb-paddles.o nix/nix-filemanager.o linuxfb/aiie.o linuxfb/linux-printer.o nix/nix-clock.o diff --git a/apple/applemmu.cpp b/apple/applemmu.cpp index 22039b2..1ea08de 100644 --- a/apple/applemmu.cpp +++ b/apple/applemmu.cpp @@ -14,6 +14,12 @@ #include "globals.h" +#ifdef TEENSYDUINO +#include "teensy-clock.h" +#else +#include "nix-clock.h" +#endif + // Serializing token for MMU data #define MMUMAGIC 'M' @@ -141,6 +147,12 @@ AppleMMU::AppleMMU(AppleDisplay *display) this->display = display; this->display->setSwitches(&switches); resetRAM(); // initialize RAM, load ROM + +#ifdef TEENSYDUINO + clock = new TeensyClock((AppleMMU *)this); +#else + clock = new NixClock((AppleMMU *)this); +#endif } AppleMMU::~AppleMMU() @@ -221,6 +233,11 @@ void AppleMMU::Reset() uint8_t AppleMMU::read(uint16_t address) { + uint8_t rv = 0; + if (handleNoSlotClock(address, &rv)) { + return rv; + } + if (address >= 0xC000 && address <= 0xC0FF) { return readSwitches(address); @@ -261,6 +278,10 @@ uint8_t AppleMMU::readDirect(uint16_t address, uint8_t fromPage) void AppleMMU::write(uint16_t address, uint8_t v) { + if (handleNoSlotClock(address, NULL)) { + return; + } + if (address >= 0xC000 && address <= 0xC0FF) { return writeSwitches(address, v); @@ -297,6 +318,26 @@ void AppleMMU::write(uint16_t address, uint8_t v) } } +bool AppleMMU::handleNoSlotClock(uint16_t address, uint8_t *rv) +{ + uint8_t ah = address >> 8; + if ( ((!intcxrom || !slot3rom) && (ah == 0xc3)) || + (ah == 0xc8) ) { + if (rv) { + // It's a read attempt - we want a return value. + *rv = 0; + if (clock->read(address, rv)) { + return true; + } + } else { + clock->write(address); + return true; + } + + } + return false; +} + // FIXME: this is no longer "MMU", is it? void AppleMMU::resetDisplay() { diff --git a/apple/applemmu.h b/apple/applemmu.h index 1ea8758..2f79b2d 100644 --- a/apple/applemmu.h +++ b/apple/applemmu.h @@ -5,6 +5,7 @@ #include "appledisplay.h" #include "slot.h" #include "mmu.h" +#include "noslotclock.h" // when we read a nondeterministic result, we return FLOATING. Maybe // some day we can come back here and figure out how to return what @@ -56,6 +57,8 @@ class AppleMMU : public MMU { void setAppleKey(int8_t which, bool isDown); protected: + bool handleNoSlotClock(uint16_t address, uint8_t *rv); + void resetDisplay(); uint8_t readSwitches(uint16_t address); void writeSwitches(uint16_t address, uint8_t v); @@ -85,6 +88,8 @@ class AppleMMU : public MMU { uint16_t writePages[0x100]; bool anyKeyDown; + + NoSlotClock *clock; }; #endif diff --git a/apple/applevm.cpp b/apple/applevm.cpp index 9abf527..bf0a382 100644 --- a/apple/applevm.cpp +++ b/apple/applevm.cpp @@ -26,25 +26,12 @@ AppleVM::AppleVM() parallel = new ParallelCard(); ((AppleMMU *)mmu)->setSlot(1, parallel); -#ifdef TEENSYDUINO - teensyClock = new TeensyClock((AppleMMU *)mmu); - ((AppleMMU *)mmu)->setSlot(5, teensyClock); -#else - nixClock = new NixClock((AppleMMU *)mmu); - ((AppleMMU *)mmu)->setSlot(5, nixClock); -#endif - hd32 = new HD32((AppleMMU *)mmu); ((AppleMMU *)mmu)->setSlot(7, hd32); } AppleVM::~AppleVM() { -#ifdef TEENSYDUINO - delete teensyClock; -#else - delete nixClock; -#endif delete disk6; delete parallel; } diff --git a/apple/applevm.h b/apple/applevm.h index 60930e5..92220e4 100644 --- a/apple/applevm.h +++ b/apple/applevm.h @@ -7,11 +7,6 @@ #include "hd32.h" #include "vmkeyboard.h" #include "parallelcard.h" -#ifdef TEENSYDUINO -#include "teensy-clock.h" -#else -#include "nix-clock.h" -#endif #include "vm.h" class AppleVM : public VM { @@ -44,11 +39,6 @@ class AppleVM : public VM { protected: VMKeyboard *keyboard; ParallelCard *parallel; -#ifdef TEENSYDUINO - TeensyClock *teensyClock; -#else - NixClock *nixClock; -#endif }; diff --git a/apple/noslotclock.cpp b/apple/noslotclock.cpp new file mode 100644 index 0000000..f32983d --- /dev/null +++ b/apple/noslotclock.cpp @@ -0,0 +1,138 @@ +#include "noslotclock.h" +#include "applemmu.h" // for FLOATING + +#define initSequence 0x5CA33AC55CA33AC5LL + +/* The no-slot clock works like this... + * + * The NSC is installed in some bank of ROM memory. For our instance, + * it's 0xC300 or 0xC800. When a read or write occurs in these pages + * - and ROM isn't switched out or whatever - then this driver is + * invoked. + * + * (It's the job of applemmu to decide if the right address is being + * called to invoke the read or write here.) + * + * To get the clock to respond, we need to first pass it the init + * Sequence (above). The NSC gets one bit at a time from watching the + * transactions on the bus. So first the driver reads or writes to + * memory address (e.g.) 0xC800 or 0xC801, depending on whether it + * wants to send a 0 or 1 bit; and then, if the NSC sees all of the + * correct bits for the init sequence, it allows responses when + * reading from memory (e.g.) 0xC804. These responses are, again, one + * bit at a time of the current date and time. + */ + +NoSlotClock::NoSlotClock(AppleMMU *mmu) +{ + this->mmu = mmu; + + compareReg = initSequence; + clockReg = 0x00LL; + clockRegPtr = compareRegPtr = 0; + + regEnabled = false; + writeEnabled = true; +} + +NoSlotClock::~NoSlotClock() +{ +} + +bool NoSlotClock::read(uint8_t s, uint8_t *d) +{ + if (s & 0x04) { + return doRead(d); + } + else { + doWrite(s); + return false; + } +} + +void NoSlotClock::write(uint8_t s) +{ + if (s & 0x04) { + doRead(0); + } else { + doWrite(s); + } +} + +bool NoSlotClock::doRead(uint8_t *d) +{ + if (!regEnabled) { + compareReg = initSequence; + compareRegPtr = 0; + writeEnabled = true; + return false; + } + + if (d) { + *d = (clockReg & 0x01) ? + ((*d) | 1) : + ((*d) & ~1); + } + clockRegPtr++; + clockReg >>= 1; + if (clockRegPtr == 64) { + regEnabled = false; + clockRegPtr = 0; + } + return true; +} + +void NoSlotClock::doWrite(uint8_t address) +{ + if (!writeEnabled) { + return; + } + + if (!regEnabled) { + if ((compareReg & 0x01) == (address & 0x01)) { + compareRegPtr++; + compareReg >>= 1; + if (compareRegPtr == 64) { + regEnabled = true; + + compareRegPtr = 0; + compareReg = initSequence; + + populateClockRegister(); + } + } else { + writeEnabled = false; + } + } else { + // The NSC driver is writing a new clock time to the clock... + clockRegPtr++; + clockReg >>= 1; + if (address & 0x01) { + clockReg |= 0x8000000000000000LL; + } + if (clockRegPtr == 64) { + regEnabled = false; + + // The clockReg should now contain a BCD4 packed date like + // 0x1708071521140200 + // ... 2017, August 07, ; 21:14:02.00 + // where that is clearly suspect. Probably because 2017 + // was too far in the future when this driver was written... + + clockRegPtr = 0; + + updateClockFromRegister(); + } + } +} + +void NoSlotClock::writeNibble(uint8_t n) +{ + for (uint8_t i=0; i<4; i++) { + clockReg <<= 1; + if (n & 0x08) { + clockReg |= 1; + } + n <<= 1; + } +} diff --git a/apple/noslotclock.h b/apple/noslotclock.h new file mode 100644 index 0000000..b06b48c --- /dev/null +++ b/apple/noslotclock.h @@ -0,0 +1,37 @@ +#ifndef __NSCLOCK_H +#define __NSCLOCK_H + +#include +#include + +class AppleMMU; + +class NoSlotClock { + public: + NoSlotClock(AppleMMU *mmu); + virtual ~NoSlotClock(); + + bool read(uint8_t s, uint8_t *data); + void write(uint8_t s); + + protected: + bool doRead(uint8_t *d); + void doWrite(uint8_t address); + void writeNibble(uint8_t n); + + virtual void populateClockRegister() = 0; + virtual void updateClockFromRegister() = 0; + + protected: + AppleMMU *mmu; + + uint64_t clockReg; + uint64_t compareReg; + uint8_t clockRegPtr; + uint8_t compareRegPtr; + + bool regEnabled; + bool writeEnabled; +}; + +#endif diff --git a/nix/nix-clock.cpp b/nix/nix-clock.cpp index f546fbf..c7f4c11 100644 --- a/nix/nix-clock.cpp +++ b/nix/nix-clock.cpp @@ -1,141 +1,62 @@ #include // memset #include +#include "noslotclock.h" + #include "nix-clock.h" -#include "applemmu.h" // for FLOATING -/* - * http://apple2online.com/web_documents/prodos_technical_notes.pdf - * - * When ProDOS calls a clock card, the card deposits an ASCII string - * in the GETLN input buffer in the form: 07,04,14,22,46,57. The - * string translates as the following: - * - * 07 = the month, July - * 04 = the day of the week (00 = Sun) - * 14 = the date (00 to 31) - * 22 = the hour (00 to 23) - * 46 = the minute (00 to 59) - * 57 = the second (00 to 59) -*/ - -static void timeToProDOS(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, - uint8_t proDOStimeOut[4]) +NixClock::NixClock(AppleMMU *mmu) : NoSlotClock(mmu) { - proDOStimeOut[0] = ((year % 100) << 1) | (month >> 3); - proDOStimeOut[1] = ((month & 0x0F) << 5) | (day & 0x1F); - proDOStimeOut[2] = hour & 0x1F; - proDOStimeOut[3] = minute & 0x3F; -} - -NixClock::NixClock(AppleMMU *mmu) -{ - this->mmu = mmu; } NixClock::~NixClock() { } -bool NixClock::Serialize(int8_t fd) +void NixClock::populateClockRegister() { - return true; -} - -bool NixClock::Deserialize(int8_t fd) -{ - return true; -} - -void NixClock::Reset() -{ -} - -uint8_t NixClock::readSwitches(uint8_t s) -{ - // When any switch is read, we'll put the current time in the prodos time buffer time_t lt; time(<); struct tm *ct = localtime(<); - // Put the date/time in the official ProDOS buffer - uint8_t prodosOut[4]; - timeToProDOS(ct->tm_year+1900, - ct->tm_mon, - ct->tm_mday, - ct->tm_hour, - ct->tm_min, - prodosOut); - mmu->write(0xBF90, prodosOut[0]); - mmu->write(0xBF91, prodosOut[1]); - mmu->write(0xBF92, prodosOut[2]); - mmu->write(0xBF93, prodosOut[3]); + clockReg = 0x0; - // and also generate a date/time that contains seconds, but not a - // year, which it also consumes - char ts[18]; - sprintf(ts, "%.2d,%.2d,%.2d,%.2d,%.2d,%.2d", - ct->tm_mon, - ct->tm_wday, - ct->tm_mday, - ct->tm_hour, - ct->tm_min, - ct->tm_sec); + // BCD, 4 bits per digit. - uint8_t i = 0; - while (ts[i]) { - mmu->write(0x200 + i, ts[i] | 0x80); - i++; - } + ct->tm_year %= 100; // must be 00-99 + writeNibble(ct->tm_year / 10); + writeNibble(ct->tm_year % 10); - return FLOATING; + ct->tm_mon++; // 1 = January + writeNibble(ct->tm_mon / 10); + writeNibble(ct->tm_mon % 10); + + writeNibble(ct->tm_mday / 10); + writeNibble(ct->tm_mday % 10);// day of month, 1-31 + + writeNibble(0); + writeNibble(ct->tm_wday + 1); // day of week, 1-7 + + writeNibble(ct->tm_hour / 10); + writeNibble(ct->tm_hour % 10); + + writeNibble(ct->tm_min / 10); + writeNibble(ct->tm_min % 10); + + writeNibble(ct->tm_sec / 10); // tens of seconds + writeNibble(ct->tm_sec % 10); // ones of seconds, 00-99 + + writeNibble(0); // ones of milliseconds, 00-99 + writeNibble(0); // tens of milliseconds } -void NixClock::writeSwitches(uint8_t s, uint8_t v) +void NixClock::updateClockFromRegister() { - // printf("unimplemented write to the clock - 0x%X\n", v); + // The clockReg should now contain a BCD4 packed date like + // 0x1708071521140200 + // ... 2017, August 07, ; 21:14:02.00 + // where that is clearly suspect. Probably because 2017 + // was too far in the future when this driver was written... + + printf(">> Got a request to set clock: 0x%llX\n", clockReg); } - -// FIXME: this assumes slot #5 -void NixClock::loadROM(uint8_t *toWhere) -{ - memset(toWhere, 0xEA, 256); // fill the page with NOPs - - // ProDOS only needs these 4 bytes to recognize that a clock is present - toWhere[0x00] = 0x08; // PHP - toWhere[0x02] = 0x28; // PLP - toWhere[0x04] = 0x58; // CLI - toWhere[0x06] = 0x70; // BVS - - // Pad out those bytes so they will return control well. The program - // at c500 becomes - // - // C500: PHP ; push to stack - // NOP ; filler (filled in by memory clear) - // PLP ; pop from stack - // RTS ; return - // CLI ; required to detect driver, but not used - // NOP ; filled in by memory clear - // BVS ; required to detect driver, but not used - - toWhere[0x03] = 0x60; // RTS - - // And it needs a small routing here to read/write it: - // 0x08: read - toWhere[0x08] = 0x4C; // JMP $C510 - toWhere[0x09] = 0x10; - toWhere[0x0A] = 0xC5; - - // 0x0b: write - toWhere[0x0B] = 0x8D; // STA $C0D0 (slot 5's first switch) - toWhere[0x0C] = 0xD0; - toWhere[0x0D] = 0xC0; - toWhere[0x0E] = 0x60; // RTS - - // simple read - toWhere[0x10] = 0xAD; // LDA $C0D0 (slot 5's first switch) - toWhere[0x11] = 0xD0; - toWhere[0x12] = 0xC0; - toWhere[0x13] = 0x60; // RTS -} - diff --git a/nix/nix-clock.h b/nix/nix-clock.h index ad508e3..482b0c0 100644 --- a/nix/nix-clock.h +++ b/nix/nix-clock.h @@ -4,28 +4,17 @@ #include #include -#include "slot.h" -#include "applemmu.h" +#include "noslotclock.h" -// Simple clock for *nix - -class NixClock : public Slot { +class NixClock : public NoSlotClock { public: NixClock(AppleMMU *mmu); virtual ~NixClock(); - virtual bool Serialize(int8_t fd); - virtual bool Deserialize(int8_t fd); + protected: + virtual void populateClockRegister(); + virtual void updateClockFromRegister(); - virtual void Reset(); - - virtual uint8_t readSwitches(uint8_t s); - virtual void writeSwitches(uint8_t s, uint8_t v); - - virtual void loadROM(uint8_t *toWhere); - - private: - AppleMMU *mmu; }; #endif diff --git a/teensy/teensy-clock.cpp b/teensy/teensy-clock.cpp index dbf9f4e..8b007e5 100644 --- a/teensy/teensy-clock.cpp +++ b/teensy/teensy-clock.cpp @@ -1,136 +1,62 @@ #include // memset #include +#include "noslotclock.h" + #include "teensy-clock.h" -#include "applemmu.h" // for FLOATING -/* - * http://apple2online.com/web_documents/prodos_technical_notes.pdf - * - * When ProDOS calls a clock card, the card deposits an ASCII string - * in the GETLN input buffer in the form: 07,04,14,22,46,57. The - * string translates as the following: - * - * 07 = the month, July - * 04 = the day of the week (00 = Sun) - * 14 = the date (00 to 31) - * 22 = the hour (00 to 23) - * 46 = the minute (00 to 59) - * 57 = the second (00 to 59) -*/ - -static void timeToProDOS(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, - uint8_t proDOStimeOut[4]) +TeensyClock::TeensyClock(AppleMMU *mmu) : NoSlotClock(mmu) { - proDOStimeOut[0] = ((year % 100) << 1) | (month >> 3); - proDOStimeOut[1] = ((month & 0x0F) << 5) | (day & 0x1F); - proDOStimeOut[2] = hour & 0x1F; - proDOStimeOut[3] = minute & 0x3F; -} - -TeensyClock::TeensyClock(AppleMMU *mmu) -{ - this->mmu = mmu; } TeensyClock::~TeensyClock() { } -bool TeensyClock::Serialize(int8_t fd) +void TeensyClock::populateClockRegister() { - return true; -} - -bool TeensyClock::Deserialize(int8_t fd) -{ - return true; -} - -void TeensyClock::Reset() -{ -} - -uint8_t TeensyClock::readSwitches(uint8_t s) -{ - // When any switch is read, we'll put the current time in the prodos time buffer - tmElements_t tm; breakTime(now(), tm); - // Put the date/time in the official ProDOS buffer - uint8_t prodosOut[4]; - timeToProDOS(tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, prodosOut); - mmu->write(0xBF90, prodosOut[0]); - mmu->write(0xBF91, prodosOut[1]); - mmu->write(0xBF92, prodosOut[2]); - mmu->write(0xBF93, prodosOut[3]); + tm.Year %= 100; // 00-99 + tm.Month++; // 1-12 + tm.Wday++; // 1-7, where 1 = Sunday - // and also generate a date/time that contains seconds, but not a - // year, which it also consumes - char ts[18]; - sprintf(ts, "%.2d,%.2d,%.2d,%.2d,%.2d,%.2d", - tm.Month, - tm.Wday - 1, // Sunday should be 0, not 1 - tm.Day, - tm.Hour, - tm.Minute, - tm.Second); - - uint8_t i = 0; - while (ts[i]) { - mmu->write(0x200 + i, ts[i] | 0x80); - i++; - } - - return FLOATING; + writeNibble(tm.Year / 10); // 00-99 + writeNibble(tm.Year % 10); + writeNibble(tm.Month / 10); // 1-12 + writeNibble(tm.Month % 10); + writeNibble(tm.Day / 10); // 1-31 + writeNibble(tm.Day % 10); + writeNibble(0); + writeNibble(tm.Wday); // 1-7, where 1 = Sunday + writeNibble(tm.Hour / 10); + writeNibble(tm.Hour % 10); + writeNibble(tm.Minute / 10); + writeNibble(tm.Minute % 10); + writeNibble(tm.Second / 10); + writeNibble(tm.Second % 10); + writeNibble(0); // 00-99 milliseconds + writeNibble(0); } -void TeensyClock::writeSwitches(uint8_t s, uint8_t v) +static uint8_t bcdToDecimal(uint8_t v) { - // printf("unimplemented write to the clock - 0x%X\n", v); + return ((v & 0x0F) + (((v & 0xF0) >> 4) * 10)); } -// FIXME: this assumes slot #5 -void TeensyClock::loadROM(uint8_t *toWhere) + +void TeensyClock::updateClockFromRegister() { - memset(toWhere, 0xEA, 256); // fill the page with NOPs + uint8_t hours, minutes, seconds, days, months; + uint16_t years; - // ProDOS only needs these 4 bytes to recognize that a clock is present - toWhere[0x00] = 0x08; // PHP - toWhere[0x02] = 0x28; // PLP - toWhere[0x04] = 0x58; // CLI - toWhere[0x06] = 0x70; // BVS + years = bcdToDecimal(clockReg & 0xFF00000000000000LL >> 56) + 2000; + months = bcdToDecimal(clockReg & 0x00FF000000000000LL >> 48) - 1; + days = bcdToDecimal(clockReg & 0x0000FF0000000000LL >> 40); + hours = bcdToDecimal(clockReg & 0x00000000FF000000LL >> 24); + minutes = bcdToDecimal(clockReg & 0x0000000000FF0000LL >> 16); + seconds = bcdToDecimal(clockReg & 0x000000000000FF00LL >> 8); - // Pad out those bytes so they will return control well. The program - // at c500 becomes - // - // C500: PHP ; push to stack - // NOP ; filler (filled in by memory clear) - // PLP ; pop from stack - // RTS ; return - // CLI ; required to detect driver, but not used - // NOP ; filled in by memory clear - // BVS ; required to detect driver, but not used - - toWhere[0x03] = 0x60; // RTS - - // And it needs a small routing here to read/write it: - // 0x08: read - toWhere[0x08] = 0x4C; // JMP $C510 - toWhere[0x09] = 0x10; - toWhere[0x0A] = 0xC5; - - // 0x0b: write - toWhere[0x0B] = 0x8D; // STA $C0D0 (slot 5's first switch) - toWhere[0x0C] = 0xD0; - toWhere[0x0D] = 0xC0; - toWhere[0x0E] = 0x60; // RTS - - // simple read - toWhere[0x10] = 0xAD; // LDA $C0D0 (slot 5's first switch) - toWhere[0x11] = 0xD0; - toWhere[0x12] = 0xC0; - toWhere[0x13] = 0x60; // RTS + setTime(hours, minutes, seconds, days, months, years); } - diff --git a/teensy/teensy-clock.h b/teensy/teensy-clock.h index 630fa3b..bb7d3b2 100644 --- a/teensy/teensy-clock.h +++ b/teensy/teensy-clock.h @@ -1,33 +1,20 @@ #ifndef __TEENSYCLOCK_H #define __TEENSYCLOCK_H -#ifdef TEENSYDUINO #include -#else -#include -#include -#endif -#include "slot.h" #include "applemmu.h" -class TeensyClock : public Slot { +#include "NoSlotClock.h" + +class TeensyClock : public NoSlotClock { public: 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); - virtual void writeSwitches(uint8_t s, uint8_t v); - - virtual void loadROM(uint8_t *toWhere); - - private: - AppleMMU *mmu; + protected: + virtual void populateClockRegister(); + virtual void updateClockFromRegister(); }; #endif