Squashed commit of

* New BIOS interface
 * New linux framebuffer version
 * Unified linuxfb and SDL with Teensy
 * Abstracted VM RAM
 * Fixed disk image corruption due to bad cache handling
 * Variable CPU speed support
This commit is contained in:
Jorj Bauer 2018-02-07 10:20:26 -05:00
parent c25f6e757e
commit 99d0c8e72c
60 changed files with 3020 additions and 846 deletions

View File

@ -142,7 +142,10 @@ uint8_t LRingBuffer::consumeByte()
uint8_t LRingBuffer::peek(int16_t idx)
{
uint16_t p = (this->ptr + idx) % this->max;
if (!this->fill)
return 0; // No data in buffer; nothing to see
uint16_t p = (this->ptr + idx) % this->fill;
return this->buffer[p];
}

View File

@ -1,22 +1,31 @@
LDFLAGS=-L/usr/local/lib
SDLLIBS=-lSDL2
SDLLIBS=-lSDL2 -lpthread
FBLIBS=-lpthread
CXXFLAGS=-Wall -I .. -I . -I apple -I sdl -I/usr/local/include/SDL2 -O3 -g
CXXFLAGS=-Wall -I/usr/include/SDL2 -I .. -I . -I apple -I nix -I sdl -I/usr/local/include/SDL2 -g -O3
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
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
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
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
SDLOBJS=sdl/sdl-speaker.o sdl/sdl-display.o sdl/sdl-keyboard.o sdl/sdl-paddles.o nix/nix-filemanager.o sdl/aiie.o sdl/sdl-printer.o nix/nix-clock.o
ROMS=apple/applemmu-rom.h apple/diskii-rom.h apple/parallel-rom.h apple/hd32-rom.h
all: sdl
.PHONY: roms
all:
@echo You want \'make sdl\' or \'make linuxfb\'.
sdl: roms $(COMMONOBJS) $(SDLOBJS)
g++ $(LDFLAGS) $(SDLLIBS) -o aiie-sdl $(COMMONOBJS) $(SDLOBJS)
linuxfb: roms $(COMMONOBJS) $(FBOBJS)
g++ $(LDFLAGS) $(FBLIBS) -o aiie-fb $(COMMONOBJS) $(FBOBJS)
clean:
rm -f *.o *~ */*.o */*~ testharness.basic testharness.verbose testharness.extended apple/diskii-rom.h apple/applemmu-rom.h apple/parallel-rom.h aiie-sdl

View File

@ -76,20 +76,13 @@ perfect. Do as you see fit :)
Environment and Libraries
-------------------------
I built this with arduino 1.8.1 and TeensyDuino 1.35.
I built this with arduino 1.8.5 and TeensyDuino 1.40.
https://www.pjrc.com/teensy/td_download.html
These libraries I'm using right from Teensy's environment: TimerOne;
SPI; EEPROM; Time; Keypad.
There's an error in the Time library. On Macs, where the filesystem is
case-insensitive, you'll have to do something like this:
<pre>
$ mv /Applications/Arduino.app/Contents/Java//hardware/teensy/avr/libraries/Time/Time.h /Applications/Arduino.app/Contents/Java//hardware/teensy/avr/libraries/Time/_Time.h
</pre>
I'm also using these libraries that don't come with TeensyDuino:
### SdFat ###

View File

@ -55,6 +55,8 @@ class AppleDisplay : public VMDisplay{
void displayTypeChanged();
const unsigned char *xlateChar(uint8_t c, bool *invert);
private:
bool deinterlaceAddress(uint16_t address, uint8_t *row, uint8_t *col);
@ -64,8 +66,6 @@ class AppleDisplay : public VMDisplay{
void Draw14HiresPixelsAt(uint16_t addr);
void Draw80LoresPixelAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset);
const unsigned char *xlateChar(uint8_t c, bool *invert);
void redraw40ColumnText(uint8_t startingY);
void redraw80ColumnText(uint8_t startingY);
void redrawHires();

View File

@ -45,7 +45,7 @@ uint8_t AppleKeyboard::translateKeyWithModifiers(uint8_t k)
k = k - 'A' + 'a';
}
if (keysDown[_CTRL]) {
if (keysDown[PK_CTRL]) {
if (k >= 'a' && k <= 'z') {
return k - 'a' + 1;
}
@ -56,7 +56,7 @@ uint8_t AppleKeyboard::translateKeyWithModifiers(uint8_t k)
return k - 'a' + 'A';
}
if (keysDown[LSHFT] || keysDown[RSHFT]) {
if (keysDown[PK_LSHFT] || keysDown[PK_RSHFT]) {
if (k >= 'a' && k <= 'z') {
return k - 'a' + 'A';
}
@ -125,15 +125,15 @@ void AppleKeyboard::keyDepressed(uint8_t k)
keyThatIsRepeating = translateKeyWithModifiers(k);
startRepeatTimer = g_cpu->cycles + STARTREPEAT;
mmu->keyboardInput(keyThatIsRepeating);
} else if (k == LA) {
} else if (k == PK_LA) {
// Special handling: apple keys
mmu->setAppleKey(0, true);
return;
} else if (k == RA) {
} else if (k == PK_RA) {
// Special handling: apple keys
mmu->setAppleKey(1, true);
return;
} else if (k == LOCK) {
} else if (k == PK_LOCK) {
// Special handling: caps lock
capsLockEnabled = !capsLockEnabled;
return;
@ -145,15 +145,15 @@ void AppleKeyboard::keyReleased(uint8_t k)
keysDown[k] = false;
// Special handling: apple keys
if (k == LA) {
if (k == PK_LA) {
mmu->setAppleKey(0, false);
return;
}
if (k == RA) {
if (k == PK_RA) {
mmu->setAppleKey(1, false);
return;
}
if (k == LOCK) {
if (k == PK_LOCK) {
// Nothing to do when the caps lock key is released.
return;
}

View File

@ -54,33 +54,68 @@ Current write page table [512 bytes] in real ram
*/
// All the pages...
// All the pages. Because we don't have enough RAM for both the
// display's DMA and the Apple's 148k (128k + ROM space), we're using
// an external SRAM for some of this. Anything that's accessed very
// often should be in the *low* pages, b/c those are in internal
// Teensy RAM. When we run out of preallocated RAM (cf. vmram.h), we
// fall over to an external 256kB SRAM (which is much slower).
//
// Zero page (and its alts) are the most used pages (the stack is in
// page 1).
//
// We want the video display pages in real RAM as much as possible,
// since blits wind up touching so much of it. If we can keep that in
// main RAM, then the blits won't try to read the external SRAM while
// the CPU is writing to it.
//
//
// After that it's all a guess. Should it be slot ROMs?
// extended RAM? Hires RAM? FIXME: do some analysis of common memory
// hotspots...
enum {
MP_ZP = 0, // page 0/1 * 2 page variants; 0..3
MP_C1 = 4, // start of 0xC1-0xCF * 2 page variants = 30, 4..33
MP_D0 = 34, // start of 0xD0-0xDF * 5 page variants = 80; 34..113
MP_E0 = 114, // start of 0xE0-0xFF * 3 page variants = 96; 114..209
MP_2 = 210, // start of 0x02-0xBF * 2 page variants = 380; 210..589
// Pages we want to fall to internal RAM:
MP_ZP = 0, // page 0/1 * 2 page variants = 4; 0..3
MP_4 = 4, // 0x04 - 0x07 (text display pages) * 2 variants = 8; 4..11
MP_20 = 12, // 0x20 - 0x5F * 2 variants = 128; 12..139
// Pages that can go to external SRAM:
MP_2 = 140, // 0x02 - 0x03 * 2 variants = 4; 140..143
MP_8 = 144, // 0x08 - 0x1F * 2 = 48; 144..191
MP_60 = 192, // 0x60 - 0xBF * 2 = 192; 192..383
MP_C1 = 384, // start of 0xC1-0xCF * 2 page variants = 30; 384-413
MP_D0 = 414, // start of 0xD0-0xDF * 5 page variants = 80; 414-493
MP_E0 = 493, // start of 0xE0-0xFF * 3 page variants = 96; 494-589
MP_C0 = 590
// = 591 pages in all (147.75k)
};
static uint16_t _pageNumberForRam(uint8_t highByte, uint8_t variant)
{
if (highByte <= 1) {
// zero page.
return highByte + (variant*2) + MP_ZP;
}
if (highByte <= 0xBF) {
// 2-0xBF 190 * 2 pages = 380 pages = 95k
if (highByte <= 3) {
return ((highByte - 2) * 2 + variant + MP_2);
}
if (highByte == 0xC0) {
if (highByte <= 7) {
return ((highByte - 4) * 2 + variant + MP_4);
}
if (highByte <= 0x1f) {
return ((highByte - 8) * 2 + variant + MP_8);
}
if (highByte <= 0x5f) {
return ((highByte - 0x20) * 2 + variant + MP_20);
}
if (highByte <= 0xbf) {
return ((highByte - 0x60) * 2 + variant + MP_60);
}
if (highByte == 0xc0) {
return MP_C0;
}
if (highByte <= 0xCF) {
// 0xC1-0xCF 15 * 2 pages = 30 pages (7.5k)
return ((highByte - 0xC1) * 2 + variant + MP_C1);
@ -1059,5 +1094,5 @@ void AppleMMU::updateMemoryPages()
void AppleMMU::setAppleKey(int8_t which, bool isDown)
{
assert(which <= 1);
g_ram.writeByte(0xC061 + which, isDown ? 0x80 : 0x00);
g_ram.writeByte((writePages[0xC0] << 8) | (0x61 + which), isDown ? 0x80 : 0x00);
}

View File

@ -3,7 +3,7 @@
#include <stdlib.h>
#include "appledisplay.h"
#include "Slot.h"
#include "slot.h"
#include "mmu.h"
// when we read a nondeterministic result, we return FLOATING. Maybe

View File

@ -55,8 +55,14 @@ void AppleUI::drawOnOffUIElement(uint8_t element, bool state)
if (element == UIeDisk2_activity)
xoff += 135;
for (int x=0; x<6; x++) {
g_display->drawPixel(x + xoff, yoff, state ? 0xF800 : 0x8AA9);
g_display->drawPixel(x + xoff, yoff + 1, state ? 0xF800 : 0x8AA9);
// Can't draw this from inside the interrupt; might already be
// drawing the screen from outside the interrupt. Temporary
// hack - remove this completely; FIXME: update diskii.cpp to
// queue it somehow, for drawing in a maintenance function, to
// be called from the main thread and not the interrupt
// g_display->drawPixel(x + xoff, yoff, state ? 0xF800 : 0x8AA9);
// g_display->drawPixel(x + xoff, yoff + 1, state ? 0xF800 : 0x8AA9);
}
}

View File

@ -30,8 +30,8 @@ AppleVM::AppleVM()
teensyClock = new TeensyClock((AppleMMU *)mmu);
((AppleMMU *)mmu)->setSlot(5, teensyClock);
#else
sdlClock = new SDLClock((AppleMMU *)mmu);
((AppleMMU *)mmu)->setSlot(5, sdlClock);
nixClock = new NixClock((AppleMMU *)mmu);
((AppleMMU *)mmu)->setSlot(5, nixClock);
#endif
hd32 = new HD32((AppleMMU *)mmu);
@ -43,7 +43,7 @@ AppleVM::~AppleVM()
#ifdef TEENSYDUINO
delete teensyClock;
#else
delete sdlClock;
delete nixClock;
#endif
delete disk6;
delete parallel;

View File

@ -10,7 +10,7 @@
#ifdef TEENSYDUINO
#include "teensy-clock.h"
#else
#include "sdl-clock.h"
#include "nix-clock.h"
#endif
#include "vm.h"
@ -47,7 +47,7 @@ class AppleVM : public VM {
#ifdef TEENSYDUINO
TeensyClock *teensyClock;
#else
SDLClock *sdlClock;
NixClock *nixClock;
#endif
};

View File

@ -28,8 +28,6 @@ DiskII::DiskII(AppleMMU *mmu)
curPhase[0] = curPhase[1] = 0;
curHalfTrack[0] = curHalfTrack[1] = 0;
trackDirty = false;
trackToRead = -1;
trackToFlush = -1;
writeMode = false;
@ -51,10 +49,6 @@ 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);
@ -81,13 +75,6 @@ bool DiskII::Serialize(int8_t fd)
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;
@ -97,10 +84,6 @@ 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;
@ -130,10 +113,7 @@ bool DiskII::Deserialize(int8_t 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;
@ -147,8 +127,6 @@ void DiskII::Reset()
curPhase[0] = curPhase[1] = 0;
curHalfTrack[0] = curHalfTrack[1] = 0;
trackDirty = false;
trackToRead = -1;
trackToFlush = -1;
writeMode = false;
@ -159,12 +137,12 @@ void DiskII::Reset()
ejectDisk(1);
}
// FIXME: why does this need an argument?
void DiskII::checkFlush(int8_t track)
{
if (trackDirty && trackToFlush == -1) {
diskToFlush = selectedDisk;
trackToFlush = track;
trackDirty = false; // just so we don't overwrite disk/track to flush before continuing...
if (trackToFlush != -1) {
flushTrack(trackToFlush, selectedDisk);
trackToFlush = -1;
}
}
@ -249,11 +227,6 @@ uint8_t DiskII::readSwitches(uint8_t s)
// Any even address read returns the readWriteLatch (UTA2E Table 9.1,
// p. 9-12, note 2)
// if ((s & 1) == 0 && curHalfTrack[selectedDisk] <= 3) {
// printf("Read: %X\n", readWriteLatch);
// fflush(stdout);
// }
return (s & 1) ? FLOATING : readWriteLatch;
}
@ -381,8 +354,11 @@ void DiskII::setPhase(uint8_t phase)
if (curHalfTrack[selectedDisk]>>1 != prevHalfTrack>>1) {
// We're changing track - flush the old track back to disk
checkFlush(prevHalfTrack>>1);
// mark the new track to be read
trackToRead = curHalfTrack[selectedDisk]>>1;
// Prime the cache by reading the current track
if (disk[selectedDisk] != -1) {
readDiskTrack(selectedDisk, curHalfTrack[selectedDisk]>>1);
}
}
}
@ -438,11 +414,19 @@ void DiskII::insertDisk(int8_t driveNum, const char *filename, bool drawIt)
// convertDskToNib("/tmp/debug.nib");
#endif
}
if (driveNum == selectedDisk) {
readDiskTrack(selectedDisk, curHalfTrack[selectedDisk]>>1);
}
}
void DiskII::ejectDisk(int8_t driveNum)
{
if (disk[driveNum] != -1) {
if (selectedDisk == driveNum) {
checkFlush(0); // FIXME: bogus argument
trackBuffer->clear();
}
g_filemanager->closeFile(disk[driveNum]);
disk[driveNum] = -1;
g_ui->drawOnOffUIElement(UIeDisk1_state + driveNum, true);
@ -463,11 +447,10 @@ void DiskII::select(int8_t which)
// set the selected disk drive
selectedDisk = which;
// trackToRead = curHalfTrack[selectedDisk]>>1;
trackToRead = -1; // Assume we don't have to read anything on the
// newly selected drive. When we get the first
// read, we'll notice the track buffer is empty...
trackBuffer->clear();
// Preread the current track
if (disk[selectedDisk] != -1) {
readDiskTrack(selectedDisk, curHalfTrack[selectedDisk]>>1);
}
}
}
@ -481,11 +464,15 @@ uint8_t DiskII::readOrWriteByte()
if (!trackBuffer->hasData()) {
// Error: writing to empty track buffer? That's a raw write w/o
// knowing where we are on the disk.
// knowing where we are on the disk. Dangerous, at very least;
// I'm not sure what the best action would be here. For the
// moment, just refuse to write it.
g_display->debugMsg("DII: unguarded write");
return GAP;
}
trackDirty = true;
trackToFlush = curHalfTrack[selectedDisk]>>1;
// It's possible that a badly behaving OS could try to write more
// data than we have buffer to handle. Don't let it. We should
// only need something like 500 bytes, at worst. In the typical
@ -520,15 +507,6 @@ uint8_t DiskII::readOrWriteByte()
return 0;
}
// trackToRead is -1 when we have a filled buffer, or we have no data at all.
// trackToRead is != -1 when we're flushing our buffer and re-filling it.
//
// Don't fill it right here, b/c we don't want to bog down the CPU
// thread/ISR.
if (trackToRead == curHalfTrack[selectedDisk]>>1) {// waiting for a read to complete for the current track
return GAP;
}
// return 0x00 every other byte. Helps the logic sequencer stay in sync.
// Otherwise we wind up waiting long periods of time for it to sync up,
// presumably because we're overrunning it (returning data faster than
@ -541,43 +519,13 @@ uint8_t DiskII::readOrWriteByte()
whitespace = !whitespace;
if ((trackToRead != -1) || !trackBuffer->hasData()) {
checkFlush(curHalfTrack[selectedDisk]>>1);
// Need to read in a track of data and nibblize it. We'll return 0xFF
// until that completes.
// This might update trackToRead with a different track than the
// one we're reading. When we finish the read, we'll need to check
// to be sure that we're still trying to read the same track that
// we started with.
trackToRead = curHalfTrack[selectedDisk]>>1;
// While we're waiting for the sector to come around, we'll return
// GAP bytes.
return GAP;
}
return trackBuffer->peekNext();
uint8_t ret = trackBuffer->peekNext();
return ret;
}
void DiskII::fillDiskBuffer()
void DiskII::readDiskTrack(int8_t diskWeAreUsing, int8_t trackWeAreReading)
{
if (trackToFlush != -1) {
flushTrack(trackToFlush, diskToFlush); // in case it's dirty: flush before changing drives
trackBuffer->clear();
trackToFlush = -1;
}
// No work to do if trackToRead is -1
if (trackToRead == -1)
return;
trackDirty = false;
int8_t trackWeAreReading = trackToRead;
int8_t diskWeAreUsing = selectedDisk;
checkFlush(trackWeAreReading); // FIXME: bogus argument
trackBuffer->clear();
trackBuffer->setPeekCursor(0);
@ -592,7 +540,7 @@ void DiskII::fillDiskBuffer()
g_filemanager->seekBlock(disk[diskWeAreUsing], trackWeAreReading * 16 + i, true);
if (!g_filemanager->readBlock(disk[diskWeAreUsing], rawTrackBuffer, true)) {
// FIXME: error handling?
trackToRead = -1;
g_display->debugMsg("DII: FM nib read failure");
return;
}
trackBuffer->addBytes(rawTrackBuffer, 416);
@ -603,22 +551,16 @@ void DiskII::fillDiskBuffer()
g_filemanager->seekBlock(disk[diskWeAreUsing], trackWeAreReading * 16, false);
if (!g_filemanager->readTrack(disk[diskWeAreUsing], rawTrackBuffer, false)) {
// FIXME: error handling?
trackToRead = -1;
g_display->debugMsg("DII: FM block read failure");
return;
}
nibblizeTrack(trackBuffer, rawTrackBuffer, diskType[diskWeAreUsing], curHalfTrack[selectedDisk]>>1);
}
}
// Make sure we're still intending to read the track we just read
if (trackWeAreReading != trackToRead ||
diskWeAreUsing != selectedDisk) {
// Abort and let it start over next time
return;
}
// Buffer is full, we're done - reset trackToRead and that will let the reads reach the CPU!
trackToRead = -1;
void DiskII::fillDiskBuffer()
{
}
const char *DiskII::DiskName(int8_t num)
@ -646,19 +588,20 @@ void DiskII::flushTrack(int8_t track, int8_t sel)
{
// safety check: if we're write-protected, then how did we get here?
if (writeProt) {
g_display->debugMsg("Write Protected");
g_display->debugMsg("DII: Write Protected");
return;
}
if (!trackBuffer->hasData()) {
// Dunno what happened - we're writing but haven't initialized the sector buffer?
g_display->debugMsg("DII: uninit'd write");
return;
}
if (diskType[sel] == nibDisk) {
// Write the whole track out exactly as we've got it. Hopefully
// someone has re-calcuated appropriate checksums on it...
g_display->debugMsg("Not writing Nib image");
g_display->debugMsg("DII: Not writing Nib image");
return;
}
@ -670,6 +613,7 @@ void DiskII::flushTrack(int8_t track, int8_t sel)
return;
case errorMissingSectors:
// The nibblized track doesn't contain all possible sectors - so it's broken. Drop the write.
g_display->debugMsg("DII: missing sectors");
trackBuffer->clear();
break;

View File

@ -10,7 +10,7 @@
#include "filemanager.h"
#include "applemmu.h"
#include "Slot.h"
#include "slot.h"
#include "LRingBuffer.h"
#include "nibutil.h"
@ -45,6 +45,7 @@ class DiskII : public Slot {
uint8_t readOrWriteByte();
void checkFlush(int8_t track);
void readDiskTrack(int8_t diskWeAreUsing, int8_t trackWeAreReading);
#ifndef TEENSYDUINO
void convertDskToNib(const char *outFN);
@ -53,7 +54,6 @@ class DiskII : public Slot {
private:
volatile int8_t curHalfTrack[2];
volatile int8_t curPhase[2];
volatile bool trackDirty; // does this track need flushing to disk?
uint8_t readWriteLatch;
LRingBuffer *trackBuffer; // nibblized data
uint8_t rawTrackBuffer[4096]; // not nibblized data
@ -65,11 +65,9 @@ class DiskII : public Slot {
int8_t disk[2];
volatile uint8_t indicatorIsOn[2];
uint8_t diskType[2];
volatile int8_t trackToRead; // -1 when we're idle; not -1 when we need to read a track.
volatile int8_t selectedDisk;
volatile int8_t trackToFlush; // -1 when there's none
volatile int8_t diskToFlush; // which selected disk are we writing to?
volatile int8_t selectedDisk;
};
#endif

View File

@ -10,7 +10,7 @@
#include "filemanager.h"
#include "applemmu.h"
#include "Slot.h"
#include "slot.h"
#include "LRingBuffer.h"

View File

@ -16,7 +16,7 @@
// time. With this off, we present a minimum number of gaps (that
// hopefully aren't too short for the ROM to be able to write
// correctly)
//#define LONGGAPS
// #define LONGGAPS
#define DISK_VOLUME 254

View File

@ -9,7 +9,7 @@
#endif
#include "applemmu.h"
#include "Slot.h"
#include "slot.h"
class Fx80;

722
bios.cpp Normal file
View File

@ -0,0 +1,722 @@
#include "globals.h"
#include "bios.h"
#include "applevm.h"
#include "physicalkeyboard.h"
#include "physicaldisplay.h"
#include "cpu.h"
enum {
ACT_EXIT = 1,
ACT_RESET = 2,
ACT_COLDBOOT = 3,
ACT_MONITOR = 4,
ACT_DISPLAYTYPE = 5,
ACT_DEBUG = 6,
ACT_DISK1 = 7,
ACT_DISK2 = 8,
ACT_HD1 = 9,
ACT_HD2 = 10,
ACT_VOLPLUS = 11,
ACT_VOLMINUS = 12,
ACT_SUSPEND = 13,
ACT_RESTORE = 14,
ACT_PRIMODE = 15,
ACT_SPEED = 16,
};
#define NUM_TITLES 3
const char *menuTitles[NUM_TITLES] = { "VM", "Hardware", "Disks" };
const uint8_t titleWidths[NUM_TITLES] = { 28, 80, 45 };
const uint8_t vmActions[] = { ACT_EXIT, ACT_RESET, ACT_COLDBOOT, ACT_MONITOR,
ACT_DEBUG, ACT_SUSPEND, ACT_RESTORE };
const uint8_t hardwareActions[] = { ACT_DISPLAYTYPE, ACT_SPEED,
ACT_PRIMODE, ACT_VOLPLUS, ACT_VOLMINUS };
const uint8_t diskActions[] = { ACT_DISK1, ACT_DISK2,
ACT_HD1, ACT_HD2 };
#define CPUSPEED_HALF 0
#define CPUSPEED_FULL 1
#define CPUSPEED_DOUBLE 2
#define CPUSPEED_QUAD 3
// FIXME: abstract the pin # rather than repeating it here
#define RESETPIN 39
const char *staticPathConcat(const char *rootPath, const char *filePath)
{
static char buf[MAXPATH];
strncpy(buf, rootPath, sizeof(buf)-1);
strncat(buf, filePath, sizeof(buf)-strlen(buf)-1);
return buf;
}
BIOS::BIOS()
{
selectedMenu = 0;
selectedMenuItem = 0;
selectedFile = -1;
for (int8_t i=0; i<BIOS_MAXFILES; i++) {
// Put end terminators in place; strncpy won't copy over them
fileDirectory[i][BIOS_MAXPATH] = '\0';
}
}
BIOS::~BIOS()
{
}
void BIOS::DrawMenuBar()
{
uint8_t xpos = 0;
if (selectedMenu < 0) {
selectedMenu = NUM_TITLES-1;
}
selectedMenu %= NUM_TITLES;
#define XPADDING 2
for (int i=0; i<NUM_TITLES; i++) {
for (int x=0; x<titleWidths[i] + 2*XPADDING; x++) {
g_display->drawPixel(xpos+x, 0, 0xFFFF);
g_display->drawPixel(xpos+x, 16, 0xFFFF);
}
for (int y=0; y<=16; y++) {
g_display->drawPixel(xpos, y, 0xFFFF);
g_display->drawPixel(xpos + titleWidths[i] + 2*XPADDING, y, 0xFFFF);
}
xpos += XPADDING;
g_display->drawString(selectedMenu == i ? M_SELECTDISABLED : M_DISABLED,
xpos, 2, menuTitles[i]);
xpos += titleWidths[i] + XPADDING;
}
}
bool BIOS::runUntilDone()
{
g_filemanager->getRootPath(rootPath, sizeof(rootPath));
// FIXME: abstract these constant speeds
currentCPUSpeedIndex = CPUSPEED_FULL;
if (g_speed == 1023000/2)
currentCPUSpeedIndex = CPUSPEED_HALF;
if (g_speed == 1023000*2)
currentCPUSpeedIndex = CPUSPEED_DOUBLE;
if (g_speed == 1023000*4)
currentCPUSpeedIndex = CPUSPEED_QUAD;
int8_t prevAction = ACT_EXIT;
bool volumeDidChange = 0;
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_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 %= 8; // FIXME: abstract max #
break;
case ACT_PRIMODE:
g_prioritizeDisplay = !g_prioritizeDisplay;
break;
case ACT_DISK1:
if (((AppleVM *)g_vm)->DiskName(0)[0] != '\0') {
((AppleVM *)g_vm)->ejectDisk(0);
} else {
if (SelectDiskImage()) {
((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()) {
((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()) {
((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()) {
((AppleVM *)g_vm)->insertHD(1, staticPathConcat(rootPath, fileDirectory[selectedFile]));
goto done;
}
}
break;
case ACT_VOLPLUS:
g_volume ++;
if (g_volume > 15) {
g_volume = 15;
}
volumeDidChange = true;
break;
case ACT_VOLMINUS:
g_volume--;
if (g_volume < 0) {
g_volume = 0;
}
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;
}
}
done:
// Undo whatever damage we've done to the screen
g_display->redraw();
AiieRect r = { 0, 0, 191, 279 };
g_display->blit(r);
// return true if any persistent setting changed that we want to store in eeprom
return volumeDidChange;
}
void BIOS::WarmReset()
{
g_cpu->Reset();
}
void BIOS::ColdReboot()
{
g_vm->Reset();
g_cpu->Reset();
}
uint8_t BIOS::GetAction(int8_t selection)
{
while (1) {
DrawMainMenu();
while (!g_keyboard->kbhit()
#ifdef TEENSYDUINO
&&
(digitalRead(RESETPIN) == HIGH)
#endif
) {
;
// Wait for either a keypress or the reset button to be pressed
}
#ifdef TEENSYDUINO
if (digitalRead(RESETPIN) == LOW) {
// wait until it's no longer pressed
while (digitalRead(RESETPIN) == HIGH)
;
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: // VM
if (isActionActive(vmActions[selectedMenuItem]))
return vmActions[selectedMenuItem];
break;
case 1: // Hardware
if (isActionActive(hardwareActions[selectedMenuItem]))
return hardwareActions[selectedMenuItem];
break;
case 2: // 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
switch (action) {
case ACT_EXIT:
case ACT_RESET:
case ACT_COLDBOOT:
case ACT_MONITOR:
case ACT_DISPLAYTYPE:
case ACT_SPEED:
case ACT_DEBUG:
case ACT_PRIMODE:
case ACT_DISK1:
case ACT_DISK2:
case ACT_HD1:
case ACT_HD2:
case ACT_SUSPEND:
case ACT_RESTORE:
return true;
case ACT_VOLPLUS:
return (g_volume < 15);
case ACT_VOLMINUS:
return (g_volume > 0);
}
/* NOTREACHED */
return false;
}
void BIOS::DrawVMMenu()
{
if (selectedMenuItem < 0)
selectedMenuItem = sizeof(vmActions)-1;
selectedMenuItem %= sizeof(vmActions);
char buf[40];
for (int i=0; i<sizeof(vmActions); i++) {
switch (vmActions[i]) {
case ACT_DEBUG:
{
const char *templateString = "Debug: %s";
switch (g_debugMode) {
case D_NONE:
sprintf(buf, templateString, "off");
break;
case D_SHOWFPS:
sprintf(buf, templateString, "Show FPS");
break;
case D_SHOWMEMFREE:
sprintf(buf, templateString, "Show mem free");
break;
case D_SHOWPADDLES:
sprintf(buf, templateString, "Show paddles");
break;
case D_SHOWPC:
sprintf(buf, templateString, "Show PC");
break;
case D_SHOWCYCLES:
sprintf(buf, templateString, "Show cycles");
break;
case D_SHOWBATTERY:
sprintf(buf, templateString, "Show battery");
break;
case D_SHOWTIME:
sprintf(buf, templateString, "Show time");
break;
}
}
break;
case ACT_EXIT:
strcpy(buf, "Resume");
break;
case ACT_RESET:
strcpy(buf, "Reset");
break;
case ACT_COLDBOOT:
strcpy(buf, "Cold Reboot");
break;
case ACT_MONITOR:
strcpy(buf, "Drop to Monitor");
break;
case ACT_SUSPEND:
strcpy(buf, "Suspend VM");
break;
case ACT_RESTORE:
strcpy(buf, "Restore VM");
break;
}
if (isActionActive(vmActions[i])) {
g_display->drawString(selectedMenuItem == i ? M_SELECTED : M_NORMAL, 10, 20 + 14 * i, buf);
} else {
g_display->drawString(selectedMenuItem == i ? M_SELECTDISABLED : M_DISABLED, 10, 20 + 14 * i, buf);
}
}
}
void BIOS::DrawHardwareMenu()
{
if (selectedMenuItem < 0)
selectedMenuItem = sizeof(hardwareActions)-1;
selectedMenuItem %= sizeof(hardwareActions);
char buf[40];
for (int i=0; i<sizeof(hardwareActions); i++) {
switch (hardwareActions[i]) {
case ACT_DISPLAYTYPE:
{
const char *templateString = "Display: %s";
switch (g_displayType) {
case m_blackAndWhite:
sprintf(buf, templateString, "B&W");
break;
case m_monochrome:
sprintf(buf, templateString, "Mono");
break;
case m_ntsclike:
sprintf(buf, templateString, "NTSC-like");
break;
case m_perfectcolor:
sprintf(buf, templateString, "RGB");
break;
}
}
break;
case ACT_SPEED:
{
const char *templateString = "CPU Speed: %s";
switch (currentCPUSpeedIndex) {
case CPUSPEED_HALF:
sprintf(buf, templateString, "Half [511.5 kHz]");
break;
case CPUSPEED_DOUBLE:
sprintf(buf, templateString, "Double (2.046 MHz)");
break;
case CPUSPEED_QUAD:
sprintf(buf, templateString, "Quad (4.092 MHz)");
break;
default:
sprintf(buf, templateString, "Normal (1.023 MHz)");
break;
}
}
break;
case ACT_PRIMODE:
if (g_prioritizeDisplay)
strcpy(buf, "Prioritize display over audio");
else
strcpy(buf, "Prioritize audio over display");
break;
case ACT_VOLPLUS:
strcpy(buf, "Volume +");
break;
case ACT_VOLMINUS:
strcpy(buf, "Volume -");
break;
}
if (isActionActive(hardwareActions[i])) {
g_display->drawString(selectedMenuItem == i ? M_SELECTED : M_NORMAL, 10, 20 + 14 * i, buf);
} else {
g_display->drawString(selectedMenuItem == i ? M_SELECTDISABLED : M_DISABLED, 10, 20 + 14 * i, buf);
}
}
// draw the volume bar
uint16_t volCutoff = 300.0 * (float)((float) g_volume / 15.0);
for (uint8_t y=234; y<=235; y++) {
for (uint16_t x = 0; x< 300; x++) {
g_display->drawPixel( x, y, x <= volCutoff ? 0xFFFF : 0x0010 );
}
}
}
void BIOS::DrawDisksMenu()
{
if (selectedMenuItem < 0)
selectedMenuItem = sizeof(diskActions)-1;
selectedMenuItem %= sizeof(diskActions);
char buf[80];
for (int i=0; i<sizeof(diskActions); i++) {
switch (diskActions[i]) {
case ACT_DISK1:
case ACT_DISK2:
{
const char *insertedDiskName = ((AppleVM *)g_vm)->DiskName(diskActions[i]==ACT_DISK2 ? 1 : 0);
// Get the name of the file; strip off the directory
const char *endPtr = &insertedDiskName[strlen(insertedDiskName)-1];
while (endPtr != insertedDiskName &&
*endPtr != '/') {
endPtr--;
}
if (*endPtr == '/') {
endPtr++;
}
if (insertedDiskName[0]) {
snprintf(buf, sizeof(buf), "Eject Disk %d [%s]", diskActions[i]==ACT_DISK2 ? 2 : 1, endPtr);
} else {
sprintf(buf, "Insert Disk %d", diskActions[i]==ACT_DISK2 ? 2 : 1);
}
}
break;
case ACT_HD1:
case ACT_HD2:
{
const char *insertedDiskName = ((AppleVM *)g_vm)->HDName(diskActions[i]==ACT_HD2 ? 1 : 0);
// Get the name of the file; strip off the directory
const char *endPtr = &insertedDiskName[strlen(insertedDiskName)-1];
while (endPtr != insertedDiskName &&
*endPtr != '/') {
endPtr--;
}
if (*endPtr == '/') {
endPtr++;
}
if (insertedDiskName[0]) {
snprintf(buf, sizeof(buf), "Remove HD %d [%s]", diskActions[i]==ACT_HD2 ? 2 : 1, endPtr);
} else {
sprintf(buf, "Connect HD %d", diskActions[i]==ACT_HD2 ? 2 : 1);
}
}
break;
}
if (isActionActive(diskActions[i])) {
g_display->drawString(selectedMenuItem == i ? M_SELECTED : M_NORMAL, 10, 20 + 14 * i, buf);
} else {
g_display->drawString(selectedMenuItem == i ? M_SELECTDISABLED : M_DISABLED, 10, 20 + 14 * i, buf);
}
}
}
void BIOS::DrawCurrentMenu()
{
switch (selectedMenu) {
case 0: // VM
DrawVMMenu();
break;
case 1: // Hardware
DrawHardwareMenu();
break;
case 2: // Disks
DrawDisksMenu();
break;
}
}
void BIOS::DrawMainMenu()
{
g_display->clrScr();
// g_display->drawString(M_NORMAL, 0, 0, "BIOS Configuration");
DrawMenuBar();
DrawCurrentMenu();
g_display->flush();
}
// 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()
{
int8_t sel = 0;
int8_t page = 0;
while (1) {
DrawDiskNames(page, sel);
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) {
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;
}
}
g_display->flush();
}
void BIOS::stripDirectory()
{
rootPath[strlen(rootPath)-1] = '\0'; // remove the last character
while (rootPath[0] && rootPath[strlen(rootPath)-1] != '/') {
rootPath[strlen(rootPath)-1] = '\0'; // remove the last character again
}
// We're either at the previous directory, or we've nulled out the whole thing.
if (rootPath[0] == '\0') {
// Never go beyond this
strcpy(rootPath, "/");
}
}
void BIOS::DrawDiskNames(uint8_t page, int8_t selection)
{
uint8_t fileCount = GatherFilenames(page);
g_display->clrScr();
g_display->drawString(M_NORMAL, 0, 12, "BIOS Configuration - pick disk");
if (page == 0) {
g_display->drawString(selection == 0 ? M_SELECTDISABLED : M_DISABLED, 10, 50, "<Prev>");
} else {
g_display->drawString(selection == 0 ? M_SELECTED : M_NORMAL, 10, 50, "<Prev>");
}
uint8_t i;
for (i=0; i<BIOS_MAXFILES; i++) {
if (i < fileCount) {
g_display->drawString((i == selection-1) ? M_SELECTED : M_NORMAL, 10, 50 + 14 * (i+1), fileDirectory[i]);
} else {
g_display->drawString((i == selection-1) ? M_SELECTDISABLED : M_DISABLED, 10, 50+14*(i+1), "-");
}
}
// FIXME: this doesn't accurately say whether or not there *are* more.
if (fileCount == BIOS_MAXFILES || fileCount == 0) {
g_display->drawString((i+1 == selection) ? M_SELECTDISABLED : M_DISABLED, 10, 50 + 14 * (i+1), "<Next>");
} else {
g_display->drawString(i+1 == selection ? M_SELECTED : M_NORMAL, 10, 50 + 14 * (i+1), "<Next>");
}
g_display->flush();
}
uint8_t BIOS::GatherFilenames(uint8_t pageOffset)
{
uint8_t startNum = 10 * pageOffset;
uint8_t count = 0; // number we're including in our listing
while (1) {
char fn[BIOS_MAXPATH];
int8_t idx = g_filemanager->readDir(rootPath, "dsk,.po,nib,img", fn, startNum + count, BIOS_MAXPATH);
if (idx == -1) {
return count;
}
idx++;
strncpy(fileDirectory[count], fn, BIOS_MAXPATH);
count++;
if (count >= BIOS_MAXFILES) {
return count;
}
}
}

54
bios.h Normal file
View File