No-Slot clock conversion

This commit is contained in:
Jorj Bauer 2018-02-17 09:01:08 -05:00
parent c2d5166272
commit f848a0cbef
11 changed files with 307 additions and 286 deletions

View File

@ -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

View File

@ -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()
{

View File

@ -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

View File

@ -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;
}

View File

@ -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
};

138
apple/noslotclock.cpp Normal file
View File

@ -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, <day of week?>; 21:14:02.00
// where that <day of week> 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;
}
}

37
apple/noslotclock.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef __NSCLOCK_H
#define __NSCLOCK_H
#include <stdint.h>
#include <stdio.h>
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

View File

@ -1,141 +1,62 @@
#include <string.h> // memset
#include <time.h>
#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(&lt);
struct tm *ct = localtime(&lt);
// 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, <day of week?>; 21:14:02.00
// where that <day of week> 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
}

View File

@ -4,28 +4,17 @@
#include <stdint.h>
#include <stdio.h>
#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

View File

@ -1,136 +1,62 @@
#include <string.h> // memset
#include <TimeLib.h>
#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);
}

View File

@ -1,33 +1,20 @@
#ifndef __TEENSYCLOCK_H
#define __TEENSYCLOCK_H
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdint.h>
#include <stdio.h>
#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