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

@ -0,0 +1,54 @@
#ifndef __BIOS_H
#define __BIOS_H
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdint.h>
#endif
#define BIOS_MAXFILES 10 // number of files in a page of listing
#define BIOS_MAXPATH 40 // maximum length of a single filename that we'll support
class BIOS {
public:
BIOS();
~BIOS();
// return true if a persistent change needs to be stored in EEPROM
bool runUntilDone();
private:
void DrawMenuBar();
void DrawCurrentMenu();
void DrawVMMenu();
void DrawHardwareMenu();
void DrawDisksMenu();
uint8_t GetAction(int8_t prevAction);
bool isActionActive(int8_t action);
void DrawMainMenu();
int8_t getCurrentMenuAction();
void WarmReset();
void ColdReboot();
bool SelectDiskImage();
void DrawDiskNames(uint8_t page, int8_t selection);
uint8_t GatherFilenames(uint8_t pageOffset);
void stripDirectory();
private:
int8_t selectedFile;
char fileDirectory[BIOS_MAXFILES][BIOS_MAXPATH+1];
char rootPath[255-BIOS_MAXPATH];
int8_t selectedMenu;
int8_t selectedMenuItem;
uint8_t currentCPUSpeedIndex;
};
#endif

View File

@ -110,6 +110,8 @@ class FileManager {
virtual uint8_t readByte(int8_t fd) = 0;
virtual bool writeByte(int8_t fd, uint8_t v) = 0;
virtual void getRootPath(char *toWhere, int8_t maxLen) = 0;
protected:
unsigned long fileSeekPositions[MAXFILES];
char cachedNames[MAXFILES][MAXPATH];

View File

@ -12,3 +12,8 @@ VMui *g_ui;
int16_t g_volume = 15;
uint8_t g_displayType = 3; // FIXME m_perfectcolor
VMRam g_ram;
volatile bool g_inInterrupt = false;
volatile uint8_t g_debugMode = D_NONE;
bool g_prioritizeDisplay = false;
volatile bool g_biosInterrupt = false;
uint32_t g_speed = 1023000; // Hz

View File

@ -11,6 +11,26 @@
#include "vmui.h"
#include "vmram.h"
// display modes
enum {
M_NORMAL = 0,
M_SELECTED = 1,
M_DISABLED = 2,
M_SELECTDISABLED = 3
};
// debug modes
enum {
D_NONE = 0,
D_SHOWFPS = 1,
D_SHOWMEMFREE = 2,
D_SHOWPADDLES = 3,
D_SHOWPC = 4,
D_SHOWCYCLES = 5,
D_SHOWBATTERY = 6,
D_SHOWTIME = 7
};
extern FileManager *g_filemanager;
extern Cpu *g_cpu;
extern VM *g_vm;
@ -23,3 +43,8 @@ extern VMui *g_ui;
extern int16_t g_volume;
extern uint8_t g_displayType;
extern VMRam g_ram;
extern volatile bool g_inInterrupt;
extern volatile uint8_t g_debugMode;
extern bool g_prioritizeDisplay;
extern volatile bool g_biosInterrupt;
extern uint32_t g_speed;

540
linuxfb/aiie.cpp Normal file
View File

@ -0,0 +1,540 @@
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <pthread.h>
#include <sys/vt.h>
#include <sys/ioctl.h>
#include <signal.h>
#include "applevm.h"
#include "fb-display.h"
#include "linux-keyboard.h"
#include "linux-speaker.h"
#include "fb-paddles.h"
#include "nix-filemanager.h"
#include "linux-printer.h"
#include "appleui.h"
#include "bios.h"
#include "globals.h"
#include "timeutil.h"
//#define SHOWFPS
//#define SHOWPC
//#define DEBUGCPU
//#define SHOWMEMPAGE
BIOS bios;
static struct timespec nextInstructionTime, startTime;
#define NB_ENABLE 1
#define NB_DISABLE 0
int send_rst = 0;
pthread_t cpuThreadID;
char disk1name[256] = "\0";
char disk2name[256] = "\0";
volatile bool wantSuspend = false;
volatile bool wantResume = false;
void doDebugging();
void sigint_handler(int n)
{
send_rst = 1;
}
void nonblock(int state)
{
struct termios ttystate;
//get the terminal state
tcgetattr(STDIN_FILENO, &ttystate);
if (state==NB_ENABLE)
{
//turn off canonical mode
ttystate.c_lflag &= ~ICANON;
//minimum of number input read.
ttystate.c_cc[VMIN] = 1;
}
else if (state==NB_DISABLE)
{
//turn on canonical mode
ttystate.c_lflag |= ICANON;
}
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
}
uint8_t read(void *arg, uint16_t address)
{
// no action; this is a dummy function until we've finished initializing...
return 0x00;
}
void write(void *arg, uint16_t address, uint8_t v)
{
// no action; this is a dummy function until we've finished initializing...
}
static void *cpu_thread(void *dummyptr) {
struct timespec currentTime;
struct timespec nextCycleTime;
uint32_t nextSpeakerCycle = 0;
#if 0
int policy;
struct sched_param param;
pthread_getschedparam(pthread_self(), &policy, &param);
param.sched_priority = sched_get_priority_max(policy);
pthread_setschedparam(pthread_self(), policy, &param);
#endif
_init_darwin_shim();
do_gettime(&startTime);
printf("Start time: %lu,%lu\n", startTime.tv_sec, startTime.tv_nsec);
do_gettime(&nextInstructionTime);
printf("free-running\n");
while (1) {
if (g_biosInterrupt) {
printf("BIOS blocking\n");
while (g_biosInterrupt) {
usleep(100);
}
printf("BIOS block complete\n");
}
if (wantSuspend) {
printf("CPU halted; suspending VM\n");
g_vm->Suspend("suspend.vm");
printf("... done; resuming CPU.\n");
wantSuspend = false;
}
if (wantResume) {
printf("CPU halted; resuming VM\n");
g_vm->Resume("suspend.vm");
printf("... done. resuming CPU.\n");
wantResume = false;
}
do_gettime(&currentTime);
/* The speaker is our priority. The CPU runs in batches anyway,
sometimes a little behind and sometimes a little ahead; but the
speaker has to be right on time. */
struct timespec diff;
#if 0
// Wait until nextSpeakerCycle
timespec_add_cycles(&startTime, nextSpeakerCycle, &nextCycleTime);
diff = tsSubtract(nextCycleTime, currentTime);
if (diff.tv_sec >= 0 || diff.tv_nsec >= 0) {
nanosleep(&diff, NULL);
}
// Speaker runs 48 cycles behind the CPU (an arbitrary number)
if (nextSpeakerCycle >= 48) {
timespec_add_cycles(&startTime, nextSpeakerCycle-48, &nextCycleTime);
uint64_t microseconds = nextCycleTime.tv_sec * 1000000 +
(double)nextCycleTime.tv_nsec / 1000.0;
g_speaker->maintainSpeaker(nextSpeakerCycle-48, microseconds);
}
// Bump speaker cycle for next go-round
nextSpeakerCycle++;
#endif
/* Next up is the CPU. */
// tsSubtract doesn't return negatives; it bounds at 0.
diff = tsSubtract(nextInstructionTime, currentTime);
uint8_t executed = 0;
if (diff.tv_sec == 0 && diff.tv_nsec == 0) {
#ifdef DEBUGCPU
executed = g_cpu->Run(1);
#else
executed = g_cpu->Run(24);
#endif
// calculate the real time that we should be at now, and schedule
// that as our next instruction time
timespec_add_cycles(&startTime, g_cpu->cycles, &nextInstructionTime);
// The paddles need to be triggered in real-time on the CPU
// clock. That happens from the VM's CPU maintenance poller.
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
#ifdef DEBUGCPU
{
uint8_t p = g_cpu->flags;
printf("OP: $%02x A: %02x X: %02x Y: %02x PC: $%04x SP: %02x Flags: %c%cx%c%c%c%c%c\n",
g_vm->getMMU()->read(g_cpu->pc),
g_cpu->a, g_cpu->x, g_cpu->y, g_cpu->pc, g_cpu->sp,
p & (1<<7) ? 'N':' ',
p & (1<<6) ? 'V':' ',
p & (1<<4) ? 'B':' ',
p & (1<<3) ? 'D':' ',
p & (1<<2) ? 'I':' ',
p & (1<<1) ? 'Z':' ',
p & (1<<0) ? 'C':' '
);
}
#endif
if (send_rst) {
#if 0
printf("Scheduling suspend request...\n");
wantSuspend = true;
#endif
#if 0
printf("Scheduling resume resume request...\n");
wantResume = true;
#endif
#if 0
printf("Sending reset\n");
g_cpu->Reset();
// testing startup keyboard presses - perform Apple //e self-test
//g_vm->getKeyboard()->keyDepressed(RA);
//g_vm->Reset();
//g_cpu->Reset();
//((AppleVM *)g_vm)->insertDisk(0, "disks/DIAGS.DSK");
#endif
#if 0
// Swap disks
if (disk1name[0] && disk2name[0]) {
printf("Swapping disks\n");
printf("Inserting disk %s in drive 1\n", disk2name);
((AppleVM *)g_vm)->insertDisk(0, disk2name);
printf("Inserting disk %s in drive 2\n", disk1name);
((AppleVM *)g_vm)->insertDisk(1, disk1name);
}
#endif
#if 0
MMU *mmu = g_vm->getMMU();
printf("PC: 0x%X\n", g_cpu->pc);
for (int i=g_cpu->pc; i<g_cpu->pc + 0x100; i++) {
printf("0x%X ", mmu->read(i));
}
printf("\n");
printf("Dropping to monitor\n");
// drop directly to monitor.
g_cpu->pc = 0xff69; // "call -151"
mmu->read(0xC054); // make sure we're in page 1
mmu->read(0xC056); // and that hires is off
mmu->read(0xC051); // and text mode is on
mmu->read(0xC08A); // and we have proper rom in place
mmu->read(0xc008); // main zero-page
mmu->read(0xc006); // rom from cards
mmu->write(0xc002 + mmu->read(0xc014)? 1 : 0, 0xff); // make sure aux ram read and write match
mmu->write(0x20, 0); // text window
mmu->write(0x21, 40);
mmu->write(0x22, 0);
mmu->write(0x23, 24);
mmu->write(0x33, '>');
mmu->write(0x48, 0); // from 0xfb2f: part of text init
#endif
send_rst = 0;
}
}
}
}
int main(int argc, char *argv[])
{
int fd;
struct vt_stat vts;
int newVT;
int initialVT;
if ((fd=open("/dev/console", O_WRONLY)) < 0) {
perror("opening /dev/console");
exit(1);
}
ioctl(fd, VT_GETSTATE, &vts);
initialVT = vts.v_active; // find what VT we were on originally
ioctl(fd, VT_OPENQRY, &newVT);
if (newVT == -1) {
printf("No VTs available");
exit(1);
}
// Switch to new VT
ioctl(fd, VT_ACTIVATE, newVT);
ioctl(0, VT_WAITACTIVE, newVT);
printf("Now on VT %d\n", newVT);
// If we want stdout/stderr to move with us to the new VT, do this sorta thing, but to the right TTY
// freopen("/dev/tty2", "w", stdout);
// freopen("/dev/tty2", "w", stderr);
// Turn off cursor
system("echo 0 > /sys/class/graphics/fbcon/cursor_blink");
#if 0
// Timing consistency check
sleep(2); // kinda random, hopefully sloppy? - to make startTime != 0,0
printf("starting time consistency check\n");
do_gettime(&startTime);
for (int i=0; i<10000000; i++) {
// Calculate the time delta from startTime to cycle # i
timespec_add_cycles(&startTime, i, &nextInstructionTime);
// Recalculate the time difference between nextInstructionTime and startTime
struct timespec runtime = tsSubtract(nextInstructionTime, startTime);
// See if it's the same as cycles_since_time
double guesstimate = cycles_since_time(&runtime);
printf("cycle %d guesstimate %f\n", i, guesstimate);
if (guesstimate != i) {
printf("FAILED: cycle %d has guesstimate %f\n", i, guesstimate);
exit(1);
}
}
printf("All ok\n");
exit(1);
#endif
g_speaker = new LinuxSpeaker();
g_printer = new LinuxPrinter();
// create the filemanager - the interface to the host file system.
g_filemanager = new NixFileManager();
g_display = new FBDisplay();
// g_displayType = m_blackAndWhite;
g_ui = new AppleUI();
// paddles have to be created after g_display created the window
g_paddles = new FBPaddles();
// Next create the virtual CPU. This needs the VM's MMU in order to run, but we don't have that yet.
g_cpu = new Cpu();
// Create the virtual machine. This may read from g_filemanager to get ROMs if necessary.
// (The actual Apple VM we've built has them compiled in, though.) It will create its virutal
// hardware (MMU, video driver, floppy, paddles, whatever).
g_vm = new AppleVM();
g_keyboard = new LinuxKeyboard(g_vm->getKeyboard());
// Now that the VM exists and it has created an MMU, we tell the CPU how to access memory through the MMU.
g_cpu->SetMMU(g_vm->getMMU());
// Now that all the virtual hardware is glued together, reset the VM
g_vm->Reset();
g_cpu->rst();
g_display->redraw();
if (argc >= 2) {
printf("Inserting disk %s\n", argv[1]);
((AppleVM *)g_vm)->insertDisk(0, argv[1]);
strcpy(disk1name, argv[1]);
}
if (argc == 3) {
printf("Inserting disk %s\n", argv[2]);
((AppleVM *)g_vm)->insertDisk(1, argv[2]);
strcpy(disk2name, argv[2]);
}
// FIXME: fixed test disk...
// ((AppleVM *)g_vm)->insertHD(0, "hd32.img");
nonblock(NB_ENABLE);
signal(SIGINT, sigint_handler);
printf("creating CPU thread\n");
if (!pthread_create(&cpuThreadID, NULL, &cpu_thread, (void *)NULL)) {
printf("thread created\n");
// pthread_setschedparam(cpuThreadID, SCHED_RR, PTHREAD_MAX_PRIORITY);
}
while (1) {
if (g_biosInterrupt) {
printf("Invoking BIOS\n");
if (bios.runUntilDone()) {
// if it returned true, we have something to store
// persistently in EEPROM.
// writePrefs();
}
printf("BIOS done\n");
// if we turned off debugMode, make sure to clear the debugMsg
if (g_debugMode == D_NONE) {
g_display->debugMsg("");
}
g_biosInterrupt = false;
// clear the CPU next-step counters
g_cpu->cycles = 0;
do_gettime(&startTime);
do_gettime(&nextInstructionTime);
// Drain the speaker queue (FIXME: a little hacky)
g_speaker->maintainSpeaker(-1, -1);
/* FIXME
// Force the display to redraw
((AppleDisplay*)(g_vm->vmdisplay))->modeChange();
*/
// Poll the keyboard before we start, so we can do selftest on startup
g_keyboard->maintainKeyboard();
}
static uint32_t usleepcycles = 16384; // step-down for display drawing. Dynamically updated based on FPS calculations.
// fill disk buffer when needed
((AppleVM*)g_vm)->disk6->fillDiskBuffer();
// Make this a little friendlier, and the expense of some framerate?
// usleep(10000);
if (g_vm->vmdisplay->needsRedraw()) {
AiieRect what = g_vm->vmdisplay->getDirtyRect();
// make sure to clear the flag before drawing; there's no lock
// on didRedraw, so the other thread might update it
g_vm->vmdisplay->didRedraw();
g_display->blit(what);
}
g_printer->update();
g_keyboard->maintainKeyboard();
g_ui->drawPercentageUIElement(UIePowerPercentage, 100);
doDebugging();
// calculate FPS & dynamically step up/down as necessary
static time_t startAt = time(NULL);
static uint32_t loopCount = 0;
loopCount++;
uint32_t lenSecs = time(NULL) - startAt;
if (lenSecs >= 5) {
float fps = loopCount / lenSecs;
#ifdef SHOWFPS
char buf[25];
sprintf(buf, "%f FPS [delay %u]", fps, usleepcycles);
g_display->debugMsg(buf);
#endif
if (fps > 60) {
usleepcycles *= 2;
} else if (fps < 40) {
usleepcycles /= 2;
}
// reset the counter & we'll adjust again in 5 seconds
loopCount = 0;
startAt = time(NULL);
}
if (usleepcycles >= 2) {
usleep(usleepcycles);
}
#ifdef SHOWPC
{
char buf[25];
sprintf(buf, "%X", g_cpu->pc);
g_display->debugMsg(buf);
}
#endif
#ifdef SHOWMEMPAGE
{
char buf[40];
sprintf(buf, "AUX %c/%c BNK %d BSR %c/%c ZP %c 80 %c INT %c",
g_vm->auxRamRead?'R':'_',
g_vm->auxRamWrite?'W':'_',
g_vm->bank1,
g_vm->readbsr ? 'R':'_',
g_vm->writebsr ? 'W':'_',
g_vm->altzp ? 'Y':'_',
g_vm->_80store ? 'Y' : '_',
g_vm->intcxrom ? 'Y' : '_');
g_display->debugMsg(buf);
}
#endif
}
}
void doDebugging()
{
char buf[25];
static time_t startAt = time(NULL);
static uint32_t loopCount = 0;
switch (g_debugMode) {
case D_SHOWFPS:
{
// display some FPS data
loopCount++;
uint32_t lenSecs = time(NULL) - startAt;
if (lenSecs >= 5) {
sprintf(buf, "%u FPS", loopCount / lenSecs);
g_display->debugMsg(buf);
startAt = time(NULL);
loopCount = 0;
}
}
break;
case D_SHOWMEMFREE:
// sprintf(buf, "%lu %u", FreeRamEstimate(), heapSize());
// g_display->debugMsg(buf);
break;
case D_SHOWPADDLES:
sprintf(buf, "%u %u", g_paddles->paddle0(), g_paddles->paddle1());
g_display->debugMsg(buf);
break;
case D_SHOWPC:
sprintf(buf, "%X", g_cpu->pc);
g_display->debugMsg(buf);
break;
case D_SHOWCYCLES:
sprintf(buf, "%X", g_cpu->cycles);
g_display->debugMsg(buf);
break;
/*
case D_SHOWBATTERY:
// sprintf(buf, "BAT %d", analogRead(BATTERYPIN));
// g_display->debugMsg(buf);
break;
case D_SHOWTIME:
// sprintf(buf, "%.2d:%.2d:%.2d", hour(), minute(), second());
// g_display->debugMsg(buf);
break;*/
}
}

212
linuxfb/fb-display.cpp Normal file
View File

@ -0,0 +1,212 @@
#include <ctype.h> // isgraph
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include "fb-display.h"
#include "bios-font.h"
#include "images.h"
#include "globals.h"
#include "applevm.h"
#include "apple/appleui.h"
// RGB map of each of the lowres colors
const uint16_t loresPixelColors[16] = { 0x0000, // 0 black
0xC006, // 1 magenta
0x0010, // 2 dark blue
0xA1B5, // 3 purple
0x0480, // 4 dark green
0x6B4D, // 5 dark grey
0x1B9F, // 6 med blue
0x0DFD, // 7 light blue
0x92A5, // 8 brown
0xF8C5, // 9 orange
0x9555, // 10 light gray
0xFCF2, // 11 pink
0x07E0, // 12 green
0xFFE0, // 13 yellow
0x87F0, // 14 aqua
0xFFFF // 15 white
};
FBDisplay::FBDisplay()
{
fb_fd = open("/dev/fb0",O_RDWR);
//Get variable screen information
ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);
printf("bpp: %d\n", vinfo.bits_per_pixel);
//Get fixed screen information
ioctl(fb_fd, FBIOGET_FSCREENINFO, &finfo);
// This shouldn't be necessary, but I'm not seeing FBIOPUT working yet
system("fbset -xres 320 -yres 240 -depth 16");
// request what we want, rather than hoping we got it
// 16bpp 320x240 (for now)
vinfo.width = 320;
vinfo.height = 240;
vinfo.bits_per_pixel = 16;
int ret = ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vinfo);
printf("Return from FBIOPUT_VSCREENINFO: %d\n", ret);
ret = ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);
printf("Return from FBIOGET_VSCREENINFO: %d\n", ret);
printf("Screen is %d x %d @ %d\n", vinfo.width, vinfo.height, vinfo.bits_per_pixel);
screensize = vinfo.yres_virtual * finfo.line_length;
fbp = (uint8_t *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, (off_t)0);
}
FBDisplay::~FBDisplay()
{
}
void FBDisplay::redraw()
{
// primarily for the device, where it's in and out of the
// bios. Draws the background image.
printf("redraw background\n");
g_ui->drawStaticUIElement(UIeOverlay);
printf("static done\n");
if (g_vm && g_ui) {
// determine whether or not a disk is inserted & redraw each drive
g_ui->drawOnOffUIElement(UIeDisk1_state, ((AppleVM *)g_vm)->DiskName(0)[0] == '\0');
g_ui->drawOnOffUIElement(UIeDisk2_state, ((AppleVM *)g_vm)->DiskName(1)[0] == '\0');
}
printf("return\n");
}
void FBDisplay::drawImageOfSizeAt(const uint8_t *img,
uint16_t sizex, uint8_t sizey,
uint16_t wherex, uint8_t wherey)
{
for (uint8_t y=0; y<sizey; y++) {
for (uint16_t x=0; x<sizex; x++) {
const uint8_t *p = &img[(y * sizex + x)*3];
drawPixel(x+wherex, y+wherey, p[0], p[1], p[2]);
}
}
}
#define BASEX 18
#define BASEY 13
void FBDisplay::blit(AiieRect r)
{
uint8_t *videoBuffer = g_vm->videoBuffer; // FIXME: poking deep
for (uint8_t y=0; y<192; y++) {
for (uint16_t x=0; x<280; x++) {
uint16_t pixel = (y*DISPLAYRUN+x)/2;
uint8_t colorIdx;
if (x & 1) {
colorIdx = videoBuffer[pixel] & 0x0F;
} else {
colorIdx = videoBuffer[pixel] >> 4;
}
long location = (x+vinfo.xoffset+BASEX) * (vinfo.bits_per_pixel/8) + (y+vinfo.yoffset+BASEY) * finfo.line_length;
*((uint16_t*)(fbp + location)) = loresPixelColors[colorIdx];
}
}
if (overlayMessage[0]) {
drawString(M_SELECTDISABLED, 1, 240 - 16 - 12, overlayMessage);
}
}
inline uint16_t _888to565(uint8_t r, uint8_t g, uint8_t b)
{
return ( (r & 0xF8) << 8 |
( (g & 0xFC) << 3) |
( (b & 0xF8) >> 3 ) );
}
// external method
void FBDisplay::drawPixel(uint16_t x, uint16_t y, uint16_t color)
{
long location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y+vinfo.yoffset) * finfo.line_length;
*((uint16_t*)(fbp + location)) = color;
}
// external method
void FBDisplay::drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b)
{
long location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y+vinfo.yoffset) * finfo.line_length;
*((uint16_t*)(fbp + location)) = _888to565(r,g,b);
}
void FBDisplay::drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c)
{
int8_t xsize = 8,
ysize = 0x0C,
offset = 0x20;
uint16_t temp;
c -= offset;// font starts with a space
uint16_t offPixel, onPixel;
switch (mode) {
case M_NORMAL:
onPixel = 0xFFFF;
offPixel = 0x0010;
break;
case M_SELECTED:
onPixel = 0x0000;
offPixel = 0xFFFF;
break;
case M_DISABLED:
default:
onPixel = 0x7BEF;
offPixel = 0x0000;
break;
case M_SELECTDISABLED:
onPixel = 0x7BEF;
offPixel = 0xFFE0;
break;
}
temp=(c*ysize);
for (int8_t y_off = 0; y_off <= ysize; y_off++) {
uint8_t ch = BiosFont[temp];
for (int8_t x_off = 0; x_off <= xsize; x_off++) {
if (ch & (1 << (7-x_off))) {
drawPixel(x + x_off, y + y_off, onPixel);
} else {
drawPixel(x + x_off, y + y_off, offPixel);
}
}
temp++;
}
}
void FBDisplay::drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str)
{
int8_t xsize = 8; // width of a char in this font
for (int8_t i=0; i<strlen(str); i++) {
drawCharacter(mode, x, y, str[i]);
x += xsize; // fixme: any inter-char spacing?
}
}
void FBDisplay::flush()
{
}
void FBDisplay::clrScr()
{
for (uint8_t y=0; y<vinfo.height; y++) {
for (uint16_t x=0; x<vinfo.width; x++) {
drawPixel(x, y, 0x0000);
}
}
}

35
linuxfb/fb-display.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef __FB_DISPLAY_H
#define __FB_DISPLAY_H
#include <stdlib.h>
#include <linux/fb.h>
#include "physicaldisplay.h"
class FBDisplay : public PhysicalDisplay {
public:
FBDisplay();
virtual ~FBDisplay();
virtual void blit(AiieRect r);
virtual void redraw();
virtual void drawImageOfSizeAt(const uint8_t *img, uint16_t sizex, uint8_t sizey, uint16_t wherex, uint8_t wherey);
virtual void drawPixel(uint16_t x, uint16_t y, uint16_t color);
virtual void drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b);
virtual void drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c);
virtual void drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str);
virtual void flush();
virtual void clrScr();
private:
int fb_fd;
struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;
long screensize;
uint8_t *fbp;
};
#endif

29
linuxfb/fb-paddles.cpp Normal file
View File

@ -0,0 +1,29 @@
#include <stdio.h>
#include "fb-paddles.h"
#include "globals.h"
FBPaddles::FBPaddles()
{
p0 = p1 = 127;
}
FBPaddles::~FBPaddles()
{
}
void FBPaddles::startReading()
{
g_vm->triggerPaddleInCycles(0, 12 * p0);
g_vm->triggerPaddleInCycles(1, 12 * p1);
}
uint8_t FBPaddles::paddle0()
{
return p0;
}
uint8_t FBPaddles::paddle1()
{
return p1;
}

17
linuxfb/fb-paddles.h Normal file
View File

@ -0,0 +1,17 @@
#include <stdint.h>
#include "physicalpaddles.h"
class FBPaddles : public PhysicalPaddles {
public:
FBPaddles();
virtual ~FBPaddles();
virtual void startReading();
virtual uint8_t paddle0();
virtual uint8_t paddle1();
public:
uint8_t p0;
uint8_t p1;
};

221
linuxfb/linux-keyboard.cpp Normal file
View File

@ -0,0 +1,221 @@
#include "linux-keyboard.h"
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <linux/input.h>
#include "sdl-paddles.h"
#include "globals.h"
#include "physicalkeyboard.h"
LinuxKeyboard::LinuxKeyboard(VMKeyboard *k) : PhysicalKeyboard(k)
{
fd = open("/dev/input/by-path/platform-20980000.usb-usb-0:1:1.0-event-kbd",
O_RDONLY | O_NONBLOCK);
}
LinuxKeyboard::~LinuxKeyboard()
{
close(fd);
}
// FIXME: dummy value
#define BIOSKEY 254
static uint8_t keymap[] = {
0, // keycode 0 doesn't exist
ESC,
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'0',
'-',
'=',
DEL,
TAB,
'q',
'w',
'e',
'r',
't',
'y',
'u',
'i',
'o',
'p',
'[',
']',
13,
_CTRL,
'a',
's',
'd',
'f',
'g',
'h',
'j',
'k',
'l',
';',
'\'',
'`',
LSHFT,
'\\',
'z',
'x',
'c',
'v',
'b',
'n',
'm',
',',
'.',
'/',
RSHFT,
'*',
LA,
' ',
LOCK,
0, // F1,
0, // F2,
0, // F3,
0, // F4,
0, // F5,
0, // F6,
0, // F7,
0, // F8,
0, // F9,
0, // F10,
0, // numlock
0, // scrolllock
0, // HOME7
0, // UP8
0, // PGUP 9
0,
0, // LEFT4
'5', // number pad 5?
0, // RTARROW6
'+',
0, // END1
0, // DOWN2
0, // PGDN3
0, // INS
0, // DEL
0,
0,
0,
0, // F11
BIOSKEY, // F12
0,
0,
0,
0,
0,
0,
0,
RET, // number pad enter?
_CTRL, // Right control?
'/',
0, // prtscr
RA,
0,
0, // HOME
UARR,
0, // PGUP
LARR,
RARR,
0, // END
DARR,
0, // PGDN
0, // INSERT
DEL,
0,
0,
0,
0,
0,
0,
0,
0 // PAUSE
};
static uint8_t mapkeycode(uint16_t v)
{
if (v < sizeof(keymap))
return keymap[v];
else
return 0;
}
void LinuxKeyboard::maintainKeyboard()
{
struct input_event ev;
ssize_t n;
n = ::read(fd, &ev, sizeof ev);
if (n == sizeof(ev)) {
if (ev.type == EV_KEY && ev.value >= 0 && ev.value <= 2) {
uint8_t code = mapkeycode(ev.code);
if (code == BIOSKEY) {
g_biosInterrupt = true;
return;
}
if (code) {
switch (ev.value) {
case 0: // release
vmkeyboard->keyReleased(code);
break;
case 1: // press
vmkeyboard->keyDepressed(code);
break;
case 2: // autorepeat
break;
}
}
}
}
}
bool keyHitPending = false;
uint8_t keyPending;
bool LinuxKeyboard::kbhit()
{
struct input_event ev;
ssize_t n;
n = ::read(fd, &ev, sizeof ev);
if (n == sizeof(ev)) {
if (ev.type == EV_KEY && ev.value == 1) {
uint8_t code = mapkeycode(ev.code);
if (code && code != BIOSKEY) {
keyHitPending = true;
keyPending = code;
}
}
}
return keyHitPending;
}
int8_t LinuxKeyboard::read()
{
if (keyHitPending) {
keyHitPending = false;
return keyPending;
}
return 0;
}

21
linuxfb/linux-keyboard.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef __LINUX_KEYBOARD_H
#define __LINUX_KEYBOARD_H
#include "physicalkeyboard.h"
#include "vmkeyboard.h"
class LinuxKeyboard : public PhysicalKeyboard {
public:
LinuxKeyboard(VMKeyboard *k);
virtual ~LinuxKeyboard();
virtual void maintainKeyboard();
virtual bool kbhit();
virtual int8_t read();
private:
int fd;
};
#endif

21
linuxfb/linux-printer.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "linux-printer.h"
LinuxPrinter::LinuxPrinter()
{
}
LinuxPrinter::~LinuxPrinter()
{
}
void LinuxPrinter::update()
{
}
void LinuxPrinter::addLine(uint8_t *rowOfBits)
{
}
void LinuxPrinter::moveDownPixels(uint8_t p)
{
}

21
linuxfb/linux-printer.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef __LINUX_PRINTER_H
#define __LINUX_PRINTER_H
#include <stdlib.h>
#include <inttypes.h>
#include "physicalprinter.h"
class LinuxPrinter : public PhysicalPrinter {
public:
LinuxPrinter();
virtual ~LinuxPrinter();
virtual void addLine(uint8_t *rowOfBits); // must be 960 pixels wide (120 bytes)
virtual void update();
virtual void moveDownPixels(uint8_t p);
};
#endif

31
linuxfb/linux-speaker.cpp Normal file
View File

@ -0,0 +1,31 @@
#include "linux-speaker.h"
#include <pthread.h>
#include <unistd.h>
#include "globals.h"
#include "timeutil.h"
LinuxSpeaker::LinuxSpeaker()
{
}
LinuxSpeaker::~LinuxSpeaker()
{
}
void LinuxSpeaker::toggle(uint32_t c)
{
}
void LinuxSpeaker::maintainSpeaker(uint32_t c, uint64_t microseconds)
{
}
void LinuxSpeaker::beginMixing()
{
}
void LinuxSpeaker::mixOutput(uint8_t v)
{
}

21
linuxfb/linux-speaker.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef __SDLSPEAKER_H
#define __SDLSPEAKER_H
#include <stdio.h>
#include <stdint.h>
#include "physicalspeaker.h"
#define SPEAKERQUEUESIZE 64
class LinuxSpeaker : public PhysicalSpeaker {
public:
LinuxSpeaker();
virtual ~LinuxSpeaker();
virtual void toggle(uint32_t c);
virtual void maintainSpeaker(uint32_t c, uint64_t microseconds);
virtual void beginMixing();
virtual void mixOutput(uint8_t v);
};
#endif

157
linuxfb/timeutil.h Normal file
View File

@ -0,0 +1,157 @@
#include <time.h>
//#include <mach/mach_time.h>
// Derived from
// http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x
#define ORWL_NANO (+1.0E-9)
#define ORWL_GIGA UINT64_C(1000000000)
#define NANOSECONDS_PER_SECOND 1000000000UL
#define CYCLES_PER_SECOND g_speed
static double orwl_timebase = 0.0;
static uint64_t orwl_timestart = 0;
static void _init_darwin_shim(void) {
#if 0
mach_timebase_info_data_t tb = { 0 };
mach_timebase_info(&tb);
orwl_timebase = tb.numer;
orwl_timebase /= tb.denom;
orwl_timestart = mach_absolute_time();
#endif
}
static int do_gettime(struct timespec *tp) {
#if 0
double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase;
tp->tv_sec = diff * ORWL_NANO;
tp->tv_nsec = diff - (tp->tv_sec * ORWL_GIGA);
#else
clock_gettime(CLOCK_MONOTONIC, tp);
#endif
return 0;
}
// adds the number of nanoseconds that 'cycles' takes to *start and
// returns it in *out
static void timespec_add_cycles(struct timespec *start,
int32_t cycles,
struct timespec *out)
{
out->tv_sec = start->tv_sec;
out->tv_nsec = start->tv_nsec;
uint64_t nanosToAdd = (double)((double)cycles * (double) (NANOSECONDS_PER_SECOND) / (double)CYCLES_PER_SECOND);
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);
if (out->tv_nsec >= NANOSECONDS_PER_SECOND) {
out->tv_sec++ ;
out->tv_nsec -= NANOSECONDS_PER_SECOND;
}
}
static unsigned long cycles_since_time(struct timespec *start)
{
unsigned long ret = start->tv_sec * CYCLES_PER_SECOND;
ret += (double)((double)start->tv_nsec * (double)0.001023 + (double) 0.01); // 0.01 for rounding error; one cycle ~= 977517nS, and 977517 * .000001023 is only 0.999999891.
return ret;
}
// adds the number of microseconds given to *start and
// returns it in *out
static void timespec_add_us(struct timespec *start,
uint64_t micros,
struct timespec *out)
{
out->tv_sec = start->tv_sec;
out->tv_nsec = start->tv_nsec;
uint64_t nanosToAdd = micros * 1000L;
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);
if (out->tv_nsec >= 1000000000L) {
out->tv_sec++ ;
out->tv_nsec -= 1000000000L;
}
}
static void timespec_diff(struct timespec *start,
struct timespec *end,
struct timespec *diff,
bool *negative) {
struct timespec t;
if (negative)
{
*negative = false;
}
// if start > end, swizzle...
if ( (start->tv_sec > end->tv_sec) || ((start->tv_sec == end->tv_sec) && (start->tv_nsec > end->tv_nsec)) )
{
t=*start;
*start=*end;
*end=t;
if (negative)
{
*negative = true;
}
}
// assuming time_t is signed ...
if (end->tv_nsec < start->tv_nsec)
{
t.tv_sec = end->tv_sec - start->tv_sec - 1;
t.tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec;
}
else
{
t.tv_sec = end->tv_sec - start->tv_sec;
t.tv_nsec = end->tv_nsec - start->tv_nsec;
}
diff->tv_sec = t.tv_sec;
diff->tv_nsec = t.tv_nsec;
}
// tsCompare: return -1, 0, 1 for (a < b), (a == b), (a > b)
static int8_t tsCompare(struct timespec *A, struct timespec *B)
{
if (A->tv_sec < B->tv_sec)
return -1;
if (A->tv_sec > B->tv_sec)
return 1;
if (A->tv_nsec < B->tv_nsec)
return -1;
if (A->tv_nsec > B->tv_nsec)
return 1;
return 0;
}
// return time1 - time2. If time1 <= time2, then return 0.
static struct timespec tsSubtract(struct timespec time1, struct timespec time2)
{
struct timespec result;
if ((time1.tv_sec < time2.tv_sec) ||
((time1.tv_sec == time2.tv_sec) &&
(time1.tv_nsec <= time2.tv_nsec))) {/* TIME1 <= TIME2? */
result.tv_sec = result.tv_nsec = 0 ;
} else {/* TIME1 > TIME2 */
result.tv_sec = time1.tv_sec - time2.tv_sec ;
if (time1.tv_nsec < time2.tv_nsec) {
result.tv_nsec = time1.tv_nsec + 1000000000L - time2.tv_nsec ;
result.tv_sec-- ;/* Borrow a second. */
} else {
result.tv_nsec = time1.tv_nsec - time2.tv_nsec ;
}
}
return (result) ;
}

View File

@ -1,7 +1,7 @@
#include <string.h> // memset
#include <time.h>
#include "sdl-clock.h"
#include "nix-clock.h"
#include "applemmu.h" // for FLOATING
/*
@ -28,30 +28,30 @@ static void timeToProDOS(uint16_t year, uint8_t month, uint8_t day, uint8_t hour
proDOStimeOut[3] = minute & 0x3F;
}
SDLClock::SDLClock(AppleMMU *mmu)
NixClock::NixClock(AppleMMU *mmu)
{
this->mmu = mmu;
}
SDLClock::~SDLClock()
NixClock::~NixClock()
{
}
bool SDLClock::Serialize(int8_t fd)
bool NixClock::Serialize(int8_t fd)
{
return true;
}
bool SDLClock::Deserialize(int8_t fd)
bool NixClock::Deserialize(int8_t fd)
{
return true;
}
void SDLClock::Reset()
void NixClock::Reset()
{
}
uint8_t SDLClock::readSwitches(uint8_t s)
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;
@ -91,13 +91,13 @@ uint8_t SDLClock::readSwitches(uint8_t s)
return FLOATING;
}
void SDLClock::writeSwitches(uint8_t s, uint8_t v)
void NixClock::writeSwitches(uint8_t s, uint8_t v)
{
// printf("unimplemented write to the clock - 0x%X\n", v);
}
// FIXME: this assumes slot #5
void SDLClock::loadROM(uint8_t *toWhere)
void NixClock::loadROM(uint8_t *toWhere)
{
memset(toWhere, 0xEA, 256); // fill the page with NOPs

View File

@ -1,16 +1,18 @@
#ifndef __SDLCLOCK_H
#define __SDLCLOCK_H
#ifndef __NIXCLOCK_H
#define __NIXCLOCK_H
#include <stdint.h>
#include <stdio.h>
#include "Slot.h"
#include "slot.h"
#include "applemmu.h"
class SDLClock : public Slot {
// Simple clock for *nix
class NixClock : public Slot {
public:
SDLClock(AppleMMU *mmu);
virtual ~SDLClock();
NixClock(AppleMMU *mmu);
virtual ~NixClock();
virtual bool Serialize(int8_t fd);
virtual bool Deserialize(int8_t fd);

View File

@ -5,19 +5,22 @@
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <dirent.h>
#include "sdl-filemanager.h"
#include "nix-filemanager.h"
SDLFileManager::SDLFileManager()
#define ROOTDIR "./disks/"
NixFileManager::NixFileManager()
{
numCached = 0;
}
SDLFileManager::~SDLFileManager()
NixFileManager::~NixFileManager()
{
}
int8_t SDLFileManager::openFile(const char *name)
int8_t NixFileManager::openFile(const char *name)
{
// See if there's a hole to re-use...
for (int i=0; i<numCached; i++) {
@ -43,7 +46,7 @@ int8_t SDLFileManager::openFile(const char *name)
return numCached-1;
}
void SDLFileManager::closeFile(int8_t fd)
void NixFileManager::closeFile(int8_t fd)
{
// invalid fd provided?
if (fd < 0 || fd >= numCached)
@ -53,7 +56,7 @@ void SDLFileManager::closeFile(int8_t fd)
cachedNames[fd][0] = '\0';
}
const char *SDLFileManager::fileName(int8_t fd)
const char *NixFileManager::fileName(int8_t fd)
{
if (fd < 0 || fd >= numCached)
return NULL;
@ -61,13 +64,96 @@ const char *SDLFileManager::fileName(int8_t fd)
return cachedNames[fd];
}
int8_t SDLFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen)
int8_t NixFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen)
{
// not used in this version
return -1;
int idx = 1;
if (strcmp(where, ROOTDIR)) {
// First entry is always "../"
if (startIdx == 0) {
strcpy(outputFN, "../");
return 0;
}
} else {
idx = 0; // we skipped ROOTDIR
}
DIR *dirp = opendir(where);
if (!dirp)
return -1;
struct dirent *dp;
outputFN[0] = '\0';
while ((dp = readdir(dirp)) != NULL) {
if (dp->d_name[0] == '.') {
// Skip any dot files (and dot directories)
continue;
}
// FIXME: skip any non-files and non-directories
if (suffix && !(dp->d_type & DT_DIR) && strlen(dp->d_name) >= 3) {
// It's a valid file. If it doesn't match any of our suffixes,
// then skip it.
char pat[40];
strncpy(pat, suffix, sizeof(pat)); // make a working copy of the suffixes
char *fsuff = &dp->d_name[strlen(dp->d_name)-3];
if (strstr(pat, ",")) {
// We have a list of suffixes. Check each of them.
bool matchesAny = false;
char *tok = strtok((char *)pat, ",");
while (tok) {
// FIXME: assumes 3 character suffixes
if (!strncasecmp(fsuff, tok, 3)) {
matchesAny = true;
break;
}
tok = strtok(NULL, ",");
}
if (!matchesAny) {
continue;
}
} else {
// One single suffix - check it
if (strcasecmp(fsuff, suffix)) {
continue;
}
}
}
// If we get here, it's something we want to show.
if (idx == startIdx) {
// Fill in the reply
strncpy(outputFN, dp->d_name, maxlen-1);
if (dp->d_type & DT_DIR) {
// suffix
strcat(outputFN, "/");
}
break;
}
// Next!
idx++;
}
// Exited the loop - all done.
closedir(dirp);
if (!outputFN[0]) {
// didn't find any more
return -1;
}
return idx;
}
void SDLFileManager::seekBlock(int8_t fd, uint16_t block, bool isNib)
void NixFileManager::seekBlock(int8_t fd, uint16_t block, bool isNib)
{
if (fd < 0 || fd >= numCached)
return;
@ -80,7 +166,7 @@ void SDLFileManager::seekBlock(int8_t fd, uint16_t block, bool isNib)
}
bool SDLFileManager::readTrack(int8_t fd, uint8_t *toWhere, bool isNib)
bool NixFileManager::readTrack(int8_t fd, uint8_t *toWhere, bool isNib)
{
if (fd < 0 || fd >= numCached)
return false;
@ -104,7 +190,7 @@ bool SDLFileManager::readTrack(int8_t fd, uint8_t *toWhere, bool isNib)
return ret;
}
bool SDLFileManager::readBlock(int8_t fd, uint8_t *toWhere, bool isNib)
bool NixFileManager::readBlock(int8_t fd, uint8_t *toWhere, bool isNib)
{
// open, seek, read, close.
if (fd < 0 || fd >= numCached)
@ -129,7 +215,7 @@ bool SDLFileManager::readBlock(int8_t fd, uint8_t *toWhere, bool isNib)
return ret;
}
bool SDLFileManager::writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib)
bool NixFileManager::writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib)
{
// open, seek, write, close.
if (fd < 0 || fd >= numCached)
@ -159,7 +245,7 @@ bool SDLFileManager::writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib)
return true;
}
bool SDLFileManager::writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib)
bool NixFileManager::writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib)
{
// open, seek, write, close.
if (fd < 0 || fd >= numCached)
@ -188,7 +274,7 @@ bool SDLFileManager::writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib)
return true;
}
uint8_t SDLFileManager::readByteAt(int8_t fd, uint32_t pos)
uint8_t NixFileManager::readByteAt(int8_t fd, uint32_t pos)
{
if (fd < 0 || fd >= numCached)
return -1; // FIXME: error handling?
@ -215,7 +301,7 @@ uint8_t SDLFileManager::readByteAt(int8_t fd, uint32_t pos)
return v;
}
bool SDLFileManager::writeByteAt(int8_t fd, uint8_t v, uint32_t pos)
bool NixFileManager::writeByteAt(int8_t fd, uint8_t v, uint32_t pos)
{
if (fd < 0 || fd >= numCached)
return false;
@ -235,7 +321,7 @@ bool SDLFileManager::writeByteAt(int8_t fd, uint8_t v, uint32_t pos)
return ret;
}
bool SDLFileManager::writeByte(int8_t fd, uint8_t v)
bool NixFileManager::writeByte(int8_t fd, uint8_t v)
{
if (fd < 0 || fd >= numCached)
return false;
@ -263,7 +349,7 @@ bool SDLFileManager::writeByte(int8_t fd, uint8_t v)
return ret;
}
uint8_t SDLFileManager::readByte(int8_t fd)
uint8_t NixFileManager::readByte(int8_t fd)
{
if (fd < 0 || fd >= numCached)
return -1; // FIXME: error handling?
@ -293,3 +379,8 @@ uint8_t SDLFileManager::readByte(int8_t fd)
return v;
}
void NixFileManager::getRootPath(char *toWhere, int8_t maxLen)
{
strcpy(toWhere, ROOTDIR);
// strncpy(toWhere, ROOTDIR, maxLen);
}

View File

@ -1,13 +1,13 @@
#ifndef __SDL_FILEMANAGER_H
#define __SDL_FILEMANAGER_H
#ifndef __NIX_FILEMANAGER_H
#define __NIX_FILEMANAGER_H
#include "filemanager.h"
#include <stdint.h>
class SDLFileManager : public FileManager {
class NixFileManager : public FileManager {
public:
SDLFileManager();
virtual ~SDLFileManager();
NixFileManager();
virtual ~NixFileManager();
virtual int8_t openFile(const char *name);
virtual void closeFile(int8_t fd);
@ -27,6 +27,8 @@ class SDLFileManager : public FileManager {
virtual uint8_t readByte(int8_t fd);
virtual bool writeByte(int8_t fd, uint8_t v);
void getRootPath(char *toWhere, int8_t maxLen);
private:
int8_t numCached;

View File

@ -10,6 +10,7 @@ class PhysicalDisplay {
PhysicalDisplay() { overlayMessage[0] = '\0'; }
virtual ~PhysicalDisplay() {};
virtual void flush() = 0; // flush any pending drawings
virtual void redraw() = 0; // total redraw, assuming nothing
virtual void blit(AiieRect r) = 0; // redraw just the VM display area
@ -22,6 +23,8 @@ class PhysicalDisplay {
virtual void drawPixel(uint16_t x, uint16_t y, uint16_t color) = 0;
virtual void drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b) = 0;
virtual void clrScr() = 0;
protected:
char overlayMessage[40];
};

View File

@ -5,22 +5,22 @@
#include "vmkeyboard.h"
#define ESC 0x1B
#define DEL 0x7F
#define RET 0x0D
#define TAB 0x09
#define LARR 0x08 // control-H
#define RARR 0x15 // control-U
#define DARR 0x0A
#define UARR 0x0B
#define PK_ESC 0x1B
#define PK_DEL 0x7F
#define PK_RET 0x0D
#define PK_TAB 0x09
#define PK_LARR 0x08 // control-H
#define PK_RARR 0x15 // control-U
#define PK_DARR 0x0A
#define PK_UARR 0x0B
// Virtual keys
#define _CTRL 0x81
#define LSHFT 0x82
#define RSHFT 0x83
#define LOCK 0x84 // caps lock
#define LA 0x85 // left (open) apple, aka paddle0 button
#define RA 0x86 // right (closed) apple aka paddle1 button
#define PK_CTRL 0x81
#define PK_LSHFT 0x82
#define PK_RSHFT 0x83
#define PK_LOCK 0x84 // caps lock
#define PK_LA 0x85 // left (open) apple, aka paddle0 button
#define PK_RA 0x86 // right (closed) apple aka paddle1 button
class PhysicalKeyboard {
public:
@ -29,6 +29,9 @@ class PhysicalKeyboard {
virtual void maintainKeyboard() = 0;
virtual bool kbhit() = 0;
virtual int8_t read() = 0;
protected:
VMKeyboard *vmkeyboard;
};

View File

@ -9,19 +9,22 @@
#include "sdl-keyboard.h"
#include "sdl-speaker.h"
#include "sdl-paddles.h"
#include "sdl-filemanager.h"
#include "nix-filemanager.h"
#include "sdl-printer.h"
#include "appleui.h"
#include "bios.h"
#include "globals.h"
#include "timeutil.h"
#define SHOWFPS
//#define SHOWFPS
//#define SHOWPC
//#define DEBUGCPU
//#define SHOWMEMPAGE
BIOS bios;
static struct timespec nextInstructionTime, startTime;
#define NB_ENABLE 1
@ -37,7 +40,7 @@ char disk2name[256] = "\0";
volatile bool wantSuspend = false;
volatile bool wantResume = false;
volatile uint64_t hitcount = 0, misscount = 0;
void doDebugging();
void sigint_handler(int n)
{
@ -99,6 +102,14 @@ static void *cpu_thread(void *dummyptr) {
printf("free-running\n");
while (1) {
if (g_biosInterrupt) {
printf("BIOS blocking\n");
while (g_biosInterrupt) {
usleep(100);
}
printf("BIOS block complete\n");
}
if (wantSuspend) {
printf("CPU halted; suspending VM\n");
g_vm->Suspend("suspend.vm");
@ -125,10 +136,7 @@ static void *cpu_thread(void *dummyptr) {
struct timespec diff = tsSubtract(nextCycleTime, currentTime);
if (diff.tv_sec >= 0 || diff.tv_nsec >= 0) {
hitcount++;
nanosleep(&diff, NULL);
} else {
misscount++;
}
// Speaker runs 48 cycles behind the CPU (an arbitrary number)
@ -282,7 +290,7 @@ int main(int argc, char *argv[])
g_printer = new SDLPrinter();
// create the filemanager - the interface to the host file system.
g_filemanager = new SDLFileManager();
g_filemanager = new NixFileManager();
g_display = new SDLDisplay();
// g_displayType = m_blackAndWhite;
@ -338,12 +346,41 @@ int main(int argc, char *argv[])
}
while (1) {
static uint32_t usleepcycles = 16384; // step-down for display drawing. Dynamically updated based on FPS calculations.
static uint8_t ctr = 0;
if (++ctr == 0) {
printf("hit: %llu; miss: %llu; pct: %f\n", hitcount, misscount, (double)misscount / (double)(misscount + hitcount));
if (g_biosInterrupt) {
printf("Invoking BIOS\n");
if (bios.runUntilDone()) {
// if it returned true, we have something to store persistently in EEPROM.
// writePrefs();
}
printf("BIOS done\n");
// if we turned off debugMode, make sure to clear the debugMsg
if (g_debugMode == D_NONE) {
g_display->debugMsg("");
}
g_biosInterrupt = false;
// clear the CPU next-step counters
g_cpu->cycles = 0;
do_gettime(&startTime);
do_gettime(&nextInstructionTime);
// Drain the speaker queue (FIXME: a little hacky)
g_speaker->maintainSpeaker(-1, -1);
/* FIXME
// Force the display to redraw
((AppleDisplay*)(g_vm->vmdisplay))->modeChange();
*/
// Poll the keyboard before we start, so we can do selftest on startup
g_keyboard->maintainKeyboard();
}
static uint32_t usleepcycles = 16384; // step-down for display drawing. Dynamically updated based on FPS calculations.
// fill disk buffer when needed
((AppleVM*)g_vm)->disk6->fillDiskBuffer();
@ -359,6 +396,9 @@ int main(int argc, char *argv[])
g_printer->update();
g_keyboard->maintainKeyboard();
doDebugging();
g_ui->drawPercentageUIElement(UIePowerPercentage, 100);
// calculate FPS & dynamically step up/down as necessary
@ -417,3 +457,50 @@ int main(int argc, char *argv[])
}
}
void doDebugging()
{
char buf[25];
static time_t startAt = time(NULL);
static uint32_t loopCount = 0;
switch (g_debugMode) {
case D_SHOWFPS:
{
// display some FPS data
loopCount++;
uint32_t lenSecs = time(NULL) - startAt;
if (lenSecs >= 5) {
sprintf(buf, "%u FPS", loopCount / lenSecs);
g_display->debugMsg(buf);
startAt = time(NULL);
loopCount = 0;
}
}
break;
case D_SHOWMEMFREE:
// sprintf(buf, "%lu %u", FreeRamEstimate(), heapSize());
// g_display->debugMsg(buf);
break;
case D_SHOWPADDLES:
sprintf(buf, "%u %u", g_paddles->paddle0(), g_paddles->paddle1());
g_display->debugMsg(buf);
break;
case D_SHOWPC:
sprintf(buf, "%X", g_cpu->pc);
g_display->debugMsg(buf);
break;
case D_SHOWCYCLES:
sprintf(buf, "%X", g_cpu->cycles);
g_display->debugMsg(buf);
break;
/*
case D_SHOWBATTERY:
// sprintf(buf, "BAT %d", analogRead(BATTERYPIN));
// g_display->debugMsg(buf);
break;
case D_SHOWTIME:
// sprintf(buf, "%.2d:%.2d:%.2d", hour(), minute(), second());
// g_display->debugMsg(buf);
break;*/
}
}

View File

@ -50,6 +50,11 @@ SDLDisplay::~SDLDisplay()
SDL_Quit();
}
void SDLDisplay::flush()
{
SDL_RenderPresent(renderer);
}
void SDLDisplay::redraw()
{
// primarily for the device, where it's in and out of the
@ -197,8 +202,10 @@ void SDLDisplay::drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str
}
}
void SDLDisplay::debugMsg(const char *msg)
void SDLDisplay::clrScr()
{
printf("%s\n", msg);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); // set to white
SDL_RenderClear(renderer); // clear it to the selected color
SDL_RenderPresent(renderer); // perform the render
}

View File

@ -13,13 +13,6 @@
#define SDLDISPLAY_WIDTH (320*2)
#define SDLDISPLAY_HEIGHT (240*2)
enum {
M_NORMAL = 0,
M_SELECTED = 1,
M_DISABLED = 2,
M_SELECTDISABLED = 3
};
class SDLDisplay : public PhysicalDisplay {
public:
SDLDisplay();
@ -28,6 +21,8 @@ class SDLDisplay : public PhysicalDisplay {
virtual void blit(AiieRect r);
virtual void redraw();
virtual void flush();
virtual void drawImageOfSizeAt(const uint8_t *img, uint16_t sizex, uint8_t sizey, uint16_t wherex, uint8_t wherey);
virtual void drawPixel(uint16_t x, uint16_t y, uint16_t color);
@ -35,7 +30,7 @@ class SDLDisplay : public PhysicalDisplay {
virtual void drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c);
virtual void drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str);
virtual void debugMsg(const char *msg);
virtual void clrScr();
private:
SDL_Window *screen;

View File

@ -20,6 +20,13 @@ void SDLKeyboard::handleKeypress(SDL_KeyboardEvent *key)
{
bool releaseEvent = key->type == SDL_KEYUP;
if (key->type == SDL_KEYDOWN &&
key->keysym.sym == SDLK_F10) {
// Invoke BIOS
g_biosInterrupt = true;
return;
}
if ( (key->keysym.sym >= 'a' && key->keysym.sym <= 'z') ||
(key->keysym.sym >= '0' && key->keysym.sym <= '9') ||
key->keysym.sym == '-' ||
@ -54,76 +61,76 @@ void SDLKeyboard::handleKeypress(SDL_KeyboardEvent *key)
// delete key
if (key->keysym.sym == 8) {
if (releaseEvent)
vmkeyboard->keyReleased(DEL);
vmkeyboard->keyReleased(PK_DEL);
else
vmkeyboard->keyDepressed(DEL);
vmkeyboard->keyDepressed(PK_DEL);
return;
}
//modifier handling
if (key->keysym.sym == SDLK_CAPSLOCK) {
if (releaseEvent)
vmkeyboard->keyReleased(LOCK);
vmkeyboard->keyReleased(PK_LOCK);
else
vmkeyboard->keyDepressed(LOCK);
vmkeyboard->keyDepressed(PK_LOCK);
}
if (key->keysym.sym == SDLK_LSHIFT ||
key->keysym.sym == SDLK_RSHIFT) {
if (releaseEvent)
vmkeyboard->keyReleased(LSHFT);
vmkeyboard->keyReleased(PK_LSHFT);
else
vmkeyboard->keyDepressed(LSHFT);
vmkeyboard->keyDepressed(PK_LSHFT);
}
// arrows
if (key->keysym.sym == SDLK_LEFT) {
if (releaseEvent)
vmkeyboard->keyReleased(LARR);
vmkeyboard->keyReleased(PK_LARR);
else
vmkeyboard->keyDepressed(LARR);
vmkeyboard->keyDepressed(PK_LARR);
}
if (key->keysym.sym == SDLK_RIGHT) {
if (releaseEvent)
vmkeyboard->keyReleased(RARR);
vmkeyboard->keyReleased(PK_RARR);
else
vmkeyboard->keyDepressed(RARR);
vmkeyboard->keyDepressed(PK_RARR);
}
if (key->keysym.sym == SDLK_LEFT) {
if (releaseEvent)
vmkeyboard->keyReleased(LARR);
vmkeyboard->keyReleased(PK_LARR);
else
vmkeyboard->keyDepressed(LARR);
vmkeyboard->keyDepressed(PK_LARR);
}
if (key->keysym.sym == SDLK_UP) {
if (releaseEvent)
vmkeyboard->keyReleased(UARR);
vmkeyboard->keyReleased(PK_UARR);
else
vmkeyboard->keyDepressed(UARR);
vmkeyboard->keyDepressed(PK_UARR);
}
if (key->keysym.sym == SDLK_DOWN) {
if (releaseEvent)
vmkeyboard->keyReleased(DARR);
vmkeyboard->keyReleased(PK_DARR);
else
vmkeyboard->keyDepressed(DARR);
vmkeyboard->keyDepressed(PK_DARR);
}
// Paddles
if (key->keysym.sym == SDLK_LGUI) {
if (releaseEvent)
vmkeyboard->keyReleased(LA);
vmkeyboard->keyReleased(PK_LA);
else
vmkeyboard->keyDepressed(LA);
vmkeyboard->keyDepressed(PK_LA);
}
if (key->keysym.sym == SDLK_RGUI) {
if (releaseEvent)
vmkeyboard->keyReleased(RA);
vmkeyboard->keyReleased(PK_RA);
else
vmkeyboard->keyDepressed(RA);
vmkeyboard->keyDepressed(PK_RA);
}
}
@ -155,3 +162,64 @@ void SDLKeyboard::maintainKeyboard()
}
}
}
bool hasKeyPending;
uint8_t keyPending;
bool SDLKeyboard::kbhit()
{
SDL_Event event;
if (SDL_PollEvent( &event ) &&
event.type == SDL_KEYDOWN) {
SDL_KeyboardEvent *key = &event.key;
if ( (key->keysym.sym >= 'a' && key->keysym.sym <= 'z') ||
(key->keysym.sym >= '0' && key->keysym.sym <= '9') ||
key->keysym.sym == '-' ||
key->keysym.sym == '=' ||
key->keysym.sym == '[' ||
key->keysym.sym == '`' ||
key->keysym.sym == ']' ||
key->keysym.sym == '\\' ||
key->keysym.sym == ';' ||
key->keysym.sym == '\'' ||
key->keysym.sym == ',' ||
key->keysym.sym == '.' ||
key->keysym.sym == '/' ||
key->keysym.sym == ' ' ||
key->keysym.sym == 27 || // ESC
key->keysym.sym == 13 || // return
key->keysym.sym == 9) { // tab
keyPending = key->keysym.sym;
hasKeyPending = true;
} else {
switch (key->keysym.sym) {
case SDLK_UP:
keyPending = PK_UARR;
hasKeyPending = true;
break;
case SDLK_DOWN:
keyPending = PK_DARR;
hasKeyPending = true;
break;
case SDLK_RIGHT:
keyPending = PK_RARR;
hasKeyPending = true;
break;
case SDLK_LEFT:
keyPending = PK_LARR;
hasKeyPending = true;
break;
}
}
}
return hasKeyPending;
}
int8_t SDLKeyboard::read()
{
// Meh
hasKeyPending = false;
return keyPending;
}

View File

@ -13,6 +13,9 @@ class SDLKeyboard : public PhysicalKeyboard {
virtual void maintainKeyboard();
virtual bool kbhit();
virtual int8_t read();
private:
void handleKeypress(SDL_KeyboardEvent *key);
};

View File

@ -20,14 +20,19 @@ static pthread_mutex_t togmutex = PTHREAD_MUTEX_INITIALIZER;
static void audioCallback(void *unused, Uint8 *stream, int len)
{
FILE *f = (FILE *)unused;
pthread_mutex_lock(&sndmutex);
if (g_biosInterrupt) {
// While the BIOS is running, we don't put samples in the audio
// queue.
memset(stream, 0, len);
pthread_mutex_unlock(&sndmutex);
return;
}
if (bufIdx >= len) {
memcpy(stream, soundBuf, len);
fwrite(soundBuf, 1, len, f);
if (bufIdx > len) {
// move the remaining data down
memcpy(soundBuf, &soundBuf[len], bufIdx - len + 1);
@ -52,7 +57,7 @@ void ResetDCFilter(); // FIXME: remove
SDLSpeaker::SDLSpeaker()
{
toggleState = false;
mixerValue = 0x8000;
mixerValue = 0x80;
toggleCount = toggleReadPtr = toggleWritePtr = 0;
@ -66,8 +71,6 @@ SDLSpeaker::SDLSpeaker()
lastCycleCount = 0;
lastSampleCount = 0;
FILE *f = fopen("out.dat", "w");
SDL_AudioSpec audioDevice;
SDL_AudioSpec audioActual;
SDL_memset(&audioDevice, 0, sizeof(audioDevice));
@ -76,7 +79,7 @@ SDLSpeaker::SDLSpeaker()
audioDevice.channels = 1;
audioDevice.samples = 4096; // 4096 bytes @ 44100Hz is about 1/10th second out of sync - should be okay for this testing
audioDevice.callback = audioCallback;
audioDevice.userdata = (void *)f;
audioDevice.userdata = NULL;
SDL_OpenAudio(&audioDevice, &audioActual); // FIXME retval
printf("Actual: freq %d channels %d samples %d\n",
@ -87,7 +90,6 @@ SDLSpeaker::SDLSpeaker()
SDLSpeaker::~SDLSpeaker()
{
pclose(f);
}
void SDLSpeaker::toggle(uint32_t c)
@ -140,22 +142,31 @@ void SDLSpeaker::maintainSpeaker(uint32_t c, uint64_t microseconds)
bool didChange = false;
pthread_mutex_lock(&togmutex);
while (toggleCount && c >= toggleTimes[toggleReadPtr]) {
// Override the mixer with a 1-bit "Terribad" audio sample change
toggleState = !toggleState;
toggleCount--;
toggleReadPtr++;
if (toggleReadPtr >= SPEAKERQUEUESIZE)
toggleReadPtr = 0;
didChange = true;
if (c == -1 && microseconds == -1) {
// flushing
printf("Flush sound output\n");
toggleReadPtr = toggleWritePtr = 0;
toggleCount = 0;
} else {
while (toggleCount && c >= toggleTimes[toggleReadPtr]) {
// Override the mixer with a 1-bit "Terribad" audio sample change
toggleState = !toggleState;
toggleCount--;
toggleReadPtr++;
if (toggleReadPtr >= SPEAKERQUEUESIZE)
toggleReadPtr = 0;
didChange = true;
}
}
pthread_mutex_unlock(&togmutex);
// FIXME: removed all the mixing code
// Add samples from the last time to this time
// mixerValue = (toggleState ? 0x1FF : 0x00);
mixerValue = (toggleState ? 0x8000 : ~0x8000);
mixerValue = (toggleState ? 0x00 : ~0x80);
// FIXME: DC filter isn't correct yet
// mixerValue = DCFilter(mixerValue);
@ -165,14 +176,18 @@ void SDLSpeaker::maintainSpeaker(uint32_t c, uint64_t microseconds)
if (numSamples) {
lastSampleCount = sampleCount;
mixerValue >>= 12; // convert from 16 bit to 8 bit; then drop volume by 50%
pthread_mutex_lock(&sndmutex);
if (bufIdx + numSamples >= sizeof(soundBuf)) {
printf("Sound overrun!\n");
static uint8_t errcnt = 0;
if (++errcnt <= 10) {
printf("Sound overrun!\n");
}
numSamples = sizeof(soundBuf) - bufIdx - 1;
}
mixerValue >>= (8-(g_volume/2));
memset(&soundBuf[bufIdx], mixerValue, numSamples);
bufIdx += numSamples;
pthread_mutex_unlock(&sndmutex);

View File

@ -5,7 +5,7 @@
#include <stdint.h>
#include "physicalspeaker.h"
#define SPEAKERQUEUESIZE 64
#define SPEAKERQUEUESIZE 1024
class SDLSpeaker : public PhysicalSpeaker {
public:
@ -17,7 +17,7 @@ class SDLSpeaker : public PhysicalSpeaker {
virtual void beginMixing();
virtual void mixOutput(uint8_t v);
private:
int16_t mixerValue;
uint8_t mixerValue;
bool toggleState;
uint32_t toggleTimes[SPEAKERQUEUESIZE];
@ -27,8 +27,6 @@ class SDLSpeaker : public PhysicalSpeaker {
uint64_t lastCycleCount;
uint64_t lastSampleCount;
FILE *f;
};
#endif

View File

@ -6,7 +6,7 @@
#define ORWL_NANO (+1.0E-9)
#define ORWL_GIGA UINT64_C(1000000000)
#define NANOSECONDS_PER_SECOND 1000000000UL
#define CYCLES_PER_SECOND 1023000UL
#define CYCLES_PER_SECOND g_speed
static double orwl_timebase = 0.0;
static uint64_t orwl_timestart = 0;
@ -34,7 +34,7 @@ static void timespec_add_cycles(struct timespec *start,
out->tv_sec = start->tv_sec;
out->tv_nsec = start->tv_nsec;
uint64_t nanosToAdd = (double)((double)cycles * (double) (NANOSECONDS_PER_SECOND) / (double)1023000);
uint64_t nanosToAdd = (double)((double)cycles * (double) (NANOSECONDS_PER_SECOND) / (double)CYCLES_PER_SECOND);
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);

View File

@ -1,477 +0,0 @@
#include "bios.h"
#include "applevm.h"
#include "physicalkeyboard.h"
#include "teensy-keyboard.h"
#include "cpu.h"
#include "teensy-filemanager.h"
#include "teensy-display.h"
#include "globals.h"
enum {
ACT_EXIT = 0,
ACT_RESET = 1,
ACT_COLDBOOT = 2,
ACT_MONITOR = 3,
ACT_DISPLAYTYPE = 4,
ACT_DEBUG = 5,
ACT_DISK1 = 6,
ACT_DISK2 = 7,
ACT_HD1 = 8,
ACT_HD2 = 9,
ACT_VOLPLUS = 10,
ACT_VOLMINUS = 11,
ACT_SUSPEND = 12,
ACT_RESTORE = 13,
ACT_PRIMODE = 14,
NUM_ACTIONS = 15
};
const char *titles[NUM_ACTIONS] = { "Resume VM",
"Reset",
"Cold Reboot",
"Drop to Monitor",
"Display: %s",
"Debug: %s",
"%s Disk 1",
"%s Disk 2",
"%s HD 1",
"%s HD 2",
"Volume +",
"Volume -",
"Suspend",
"Restore",
"Prioritize %s"
};
// FIXME: abstract the pin # rather than repeating it here
#define RESETPIN 39
extern int16_t g_volume; // FIXME: external global. icky.
extern uint8_t debugMode; // and another. :/
extern bool g_prioritizeDisplay; // And a third!
// FIXME: and these need abstracting out of the main .ino !
enum {
D_NONE = 0,
D_SHOWFPS = 1,
D_SHOWMEMFREE = 2,
D_SHOWPADDLES = 3,
D_SHOWPC = 4,
D_SHOWCYCLES = 5,
D_SHOWBATTERY = 6,
D_SHOWTIME = 7
};
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()
{
strcpy(rootPath, "/A2DISKS/");
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()
{
}
bool BIOS::runUntilDone()
{
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_DEBUG:
debugMode++;
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();
g_display->blit({0, 0, 191, 279});
// 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(selection);
while (!((TeensyKeyboard *)g_keyboard)->kbhit() &&
(digitalRead(RESETPIN) == HIGH)) {
;
// Wait for either a keypress or the reset button to be pressed
}
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;
}
switch (((TeensyKeyboard *)g_keyboard)->read()) {
case DARR:
selection++;
selection %= NUM_ACTIONS;
break;
case UARR:
selection--;
if (selection < 0)
selection = NUM_ACTIONS-1;
break;
case RET:
if (isActionActive(selection))
return selection;
break;
}
}
}
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_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::DrawMainMenu(int8_t selection)
{
((TeensyDisplay *)g_display)->clrScr();
g_display->drawString(M_NORMAL, 0, 0, "BIOS Configuration");
for (int i=0; i<NUM_ACTIONS; i++) {
char buf[25];
if (i == ACT_DISK1 || i == ACT_DISK2) {
sprintf(buf, titles[i], ((AppleVM *)g_vm)->DiskName(i - ACT_DISK1)[0] ? "Eject" : "Insert");
} else if (i == ACT_HD1 || i == ACT_HD2) {
sprintf(buf, titles[i], ((AppleVM *)g_vm)->HDName(i - ACT_HD1)[0] ? "Eject" : "Insert");
} else if (i == ACT_DISPLAYTYPE) {
switch (g_displayType) {
case m_blackAndWhite:
sprintf(buf, titles[i], "B&W");
break;
case m_monochrome:
sprintf(buf, titles[i], "Mono");
break;
case m_ntsclike:
sprintf(buf, titles[i], "NTSC-like");
break;
case m_perfectcolor:
sprintf(buf, titles[i], "RGB");
break;
}
} else if (i == ACT_DEBUG) {
switch (debugMode) {
case D_NONE:
sprintf(buf, titles[i], "off");
break;
case D_SHOWFPS:
sprintf(buf, titles[i], "Show FPS");
break;
case D_SHOWMEMFREE:
sprintf(buf, titles[i], "Show mem free");
break;
case D_SHOWPADDLES:
sprintf(buf, titles[i], "Show paddles");
break;
case D_SHOWPC:
sprintf(buf, titles[i], "Show PC");
break;
case D_SHOWCYCLES:
sprintf(buf, titles[i], "Show cycles");
break;
case D_SHOWBATTERY:
sprintf(buf, titles[i], "Show battery");
break;
case D_SHOWTIME:
sprintf(buf, titles[i], "Show time");
break;
}
} else if (i == ACT_PRIMODE) {
if (g_prioritizeDisplay)
sprintf(buf, titles[i], "display");
else
sprintf(buf, titles[i], "r/t audio");
} else {
strcpy(buf, titles[i]);
}
if (isActionActive(i)) {
g_display->drawString(selection == i ? M_SELECTED : M_NORMAL, 10, 20 + 14 * i, buf);
} else {
g_display->drawString(selection == 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++) {
((TeensyDisplay *)g_display)->moveTo(10, y);
for (uint16_t x = 0; x< 300; x++) {
((TeensyDisplay *)g_display)->drawNextPixel( x <= volCutoff ? 0xFFFF : 0x0010 );
}
}
}
// 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 (!((TeensyKeyboard *)g_keyboard)->kbhit())
;
switch (((TeensyKeyboard *)g_keyboard)->read()) {
case DARR:
sel++;
sel %= BIOS_MAXFILES + 2;
break;
case UARR:
sel--;
if (sel < 0)
sel = BIOS_MAXFILES + 1;
break;
case 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;
return true;
}
}
break;
}
}
}
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);
((TeensyDisplay *)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>");
}
}
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;
}
}
}

1
teensy/bios.cpp Symbolic link
View File

@ -0,0 +1 @@
../bios.cpp

View File

@ -1,38 +0,0 @@
#ifndef __BIOS_H
#define __BIOS_H
#include <Arduino.h>
#define BIOS_MAXFILES 10 // number of files in a page of listing
#define BIOS_MAXPATH 40 // maximum length of a single filename that we'll support
class BIOS {
public:
BIOS();
~BIOS();
// return true if a persistent change needs to be stored in EEPROM
bool runUntilDone();
private:
uint8_t GetAction(int8_t prevAction);
bool isActionActive(int8_t action);
void DrawMainMenu(int8_t selection);
void WarmReset();
void ColdReboot();
bool SelectDiskImage();
void DrawDiskNames(uint8_t page, int8_t selection);
uint8_t GatherFilenames(uint8_t pageOffset);
void stripDirectory();
private:
int8_t selectedFile;
char fileDirectory[BIOS_MAXFILES][BIOS_MAXPATH+1];
char rootPath[255-BIOS_MAXPATH];
};
#endif

1
teensy/bios.h Symbolic link
View File

@ -0,0 +1 @@
../bios.h

218
teensy/parallelsram.cpp Normal file
View File

@ -0,0 +1,218 @@
#include "parallelsram.h"
// Assumes any Output Enable pin is hardwired-enabled;
// any Chip Enable pin is hardwared-enabled.
//
// Uses the low 8 bits of Port D as I/O lines (2, 14, 7, 8, 6, 20, 21, 5).
//
// R/W (aka WriteEnable) is on pin 31.
#define RAM_RW 34
// The Address pins (19 of them). It would be nice to have these
// easily bitwise-manipulable, instead of having to set each bit
// individually.
//
// We can use 12 bits of Port C: 15, 22, 23, 9, 10, 13, 11, 12, 35, 36, 37, 38
// And then 6 bits of Port B: 16 17 19 18 49 50
//
// And hard wire one bit low (we don't need all 19 lines). That gets us
// 256 Kb of RAM which should be sufficient.
static uint8_t addrPins[] = { 15, 22, 23, 9, 10, 13, 11, 12, 35, 36, 37, 38,
16, 17, 19, 18, 49, 50
};
#if 0
#define DELAY { delayMicroseconds(1); /* overkill, but useful for debugging */ }
#else
#define DELAY { __asm__ volatile ("nop"); __asm__ volatile ("nop"); \
__asm__ volatile ("nop"); __asm__ volatile ("nop"); \
__asm__ volatile ("nop"); __asm__ volatile ("nop"); \
__asm__ volatile ("nop"); __asm__ volatile ("nop"); \
}
#endif
#define OE_ON { /*if (noe != 255) {digitalWrite(noe, LOW);}*/ }
#define OE_OFF { /*if (noe != 255) {digitalWrite(noe, HIGH);}*/ }
#define CE_ON { /*if (n_ce != 255) {digitalWrite(n_ce, LOW);} if (p_ce != 255) { digitalWrite(p_ce, HIGH); }*/ }
#define CE_OFF { /*if (n_ce != 255) {digitalWrite(n_ce, HIGH);} if (p_ce != 255) { digitalWrite(p_ce, LOW); }*/ }
#define WE_ON { digitalWriteFast(RAM_RW, LOW); }
#define WE_OFF { digitalWriteFast(RAM_RW, HIGH); }
ParallelSRAM::ParallelSRAM()
{
pinMode(RAM_RW, OUTPUT);
// Port D is our I/O port. Use the AVR emulation layer to set up the
// pins once, and then we'll just fiddle with the DDR, input, and
// output directly.
// Enable it as a digital port...
// SIM_SCGC5 |= SIM_SCGC5_PORTD;
//... what else? How do we set PORTD_PCR[0-7]?
pinMode(2, INPUT);
pinMode(14, INPUT);
pinMode(7, INPUT);
pinMode(8, INPUT);
pinMode(6, INPUT);
pinMode(20, INPUT);
pinMode(21, INPUT);
pinMode(5, INPUT);
isInput = true;
// Set up the address pins
for (int i=0; i<sizeof(addrPins); i++) {
pinMode(addrPins[i], INPUT); // disable pull-ups
pinMode(addrPins[i], OUTPUT);
digitalWrite(addrPins[i], LOW);
}
}
void ParallelSRAM::SetPins()
{
pinMode(RAM_RW, OUTPUT);
// Port D is our I/O port. Use the AVR emulation layer to set up the
// pins once, and then we'll just fiddle with the DDR, input, and
// output directly.
// Enable it as a digital port...
// SIM_SCGC5 |= SIM_SCGC5_PORTD;
//... what else? How do we set PORTD_PCR[0-7]?
pinMode(2, INPUT);
pinMode(14, INPUT);
pinMode(7, INPUT);
pinMode(8, INPUT);
pinMode(6, INPUT);
pinMode(20, INPUT);
pinMode(21, INPUT);
pinMode(5, INPUT);
isInput = true;
// Set up the address pins
for (int i=0; i<sizeof(addrPins); i++) {
pinMode(addrPins[i], INPUT); // disable pull-ups
pinMode(addrPins[i], OUTPUT);
digitalWrite(addrPins[i], LOW);
}
}
ParallelSRAM::~ParallelSRAM()
{
}
uint8_t ParallelSRAM::read(uint32_t addr)
{
cli();
// Read cycle 2
setAddress(addr);
// make sure address is valid before CE is asserted
DELAY;
CE_ON;
OE_ON;
DELAY;
uint8_t ret = getInput();
// Optional; can leave these lines asserted...
OE_OFF;
CE_OFF;
sei();
return ret;
}
void ParallelSRAM::write(uint32_t addr, uint8_t v)
{
cli();
setAddress(addr);
DELAY;
WE_ON;
CE_ON;
setOutput(v);
DELAY;
CE_OFF;
WE_OFF;
sei();
}
uint8_t ParallelSRAM::getInput()
{
if (!isInput) {
#if 1
// Directly set the direction bits. The rest of the port setup
// should be fine from the initial config.
*(volatile uint8_t *)(&GPIOD_PDDR) = 0x00; // inputs
#else
pinMode(2, INPUT);
pinMode(14, INPUT);
pinMode(7, INPUT);
pinMode(8, INPUT);
pinMode(6, INPUT);
pinMode(20, INPUT);
pinMode(21, INPUT);
pinMode(5, INPUT);
#endif
isInput = true;
}
return GPIOD_PDIR & 0xFF;
}
void ParallelSRAM::setOutput(uint8_t v)
{
// FIXME: is there a faster way to do this?
if (isInput) {
#if 1
// FIMXE: would this be correct?
*(volatile uint8_t *)(&GPIOD_PDDR) |= 0xFF; // outputs
#else
pinMode(2, OUTPUT);
pinMode(14, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(6, OUTPUT);
pinMode(20, OUTPUT);
pinMode(21, OUTPUT);
pinMode(5, OUTPUT);
#endif
isInput = false;
}
// Directly set the low 8 bits of D.
*(volatile uint8_t *)(&GPIOD_PDOR) = v;
}
void ParallelSRAM::setAddress(uint32_t addr)
{
// The low 12 bits of the address go right in to Port C. Set these
// by doing a clear of the bitmask, and then set the bits...
GPIOC_PCOR = 0x00000FFF;
GPIOC_PSOR = (addr & 0xFFF);
// The high 6 bits of the address go in to Port B, bits 0..5.
// We do that the same way...
GPIOB_PCOR = 0x0000003F;
GPIOB_PSOR = (addr >> 12);
#if 0
for (uint8_t i=0; i<sizeof(addrPins); i++) {
digitalWrite(addrPins[i],
addr & (1 << i) ? HIGH : LOW);
}
#endif
}

25
teensy/parallelsram.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef __PARALLELSRAM_H
#define __PARALLELSRAM_H
#include <Arduino.h>
class ParallelSRAM {
public:
ParallelSRAM();
~ParallelSRAM();
void SetPins();
uint8_t read(uint32_t addr);
void write(uint32_t addr, uint8_t v);
protected:
uint8_t getInput();
void setOutput(uint8_t v);
void setAddress(uint32_t addr);
private:
bool isInput;
};
#endif

View File

@ -436,18 +436,31 @@ void TeensyDisplay::drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c)
}
temp=(c*ysize);
// FIXME: the embedded moveTo() and setPixel() calls *should* work
// -- and do, for the most part. But in the BIOS they cut off after
// about half the screen. Using drawPixel() is substantially less
// efficient, but works properly.
for (int8_t y_off = 0; y_off <= ysize; y_off++) {
moveTo(x, y + y_off);
//moveTo(x, y + y_off); // does a cbi(P_CS, B_CS)
uint8_t ch = pgm_read_byte(&BiosFont[temp]);
for (int8_t x_off = 0; x_off <= xsize; x_off++) {
if (ch & (1 << (7-x_off))) {
setPixel(onPixel);
drawPixel(x+x_off, y+y_off, onPixel);
//setPixel(onPixel);
} else {
setPixel(offPixel);
drawPixel(x+x_off, y+y_off, offPixel);
//setPixel(offPixel);
}
}
temp++;
}
// Need to leave cbi set for the next draw operation. Particularly important
// on startup, when transitioning from '@' to 'Apple //e', while also drawing
// overlay text.
cbi(P_CS, B_CS);
}
void TeensyDisplay::drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str)

View File

@ -7,13 +7,6 @@
#define TEENSY_DHEIGHT 240
#define TEENSY_DWIDTH 320
enum {
M_NORMAL = 0,
M_SELECTED = 1,
M_DISABLED = 2,
M_SELECTDISABLED = 3
};
#define regtype volatile uint8_t
#define regsize uint8_t
@ -40,7 +33,8 @@ class TeensyDisplay : public PhysicalDisplay {
virtual void blit(AiieRect r);
virtual void redraw();
void clrScr();
virtual void clrScr();
virtual void flush() {};
virtual void drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c);
virtual void drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str);

View File

@ -115,7 +115,7 @@ int8_t TeensyFileManager::readDir(const char *where, const char *suffix, char *o
// multiple suffixes to check - all must be 3 chars long, FIXME
bool matchesAny = false;
const char *p = suffix;
while (p && strlen(p)) {
while (*p && strlen(p)) {
if (!strncasecmp(fsuff, p, 3)) {
matchesAny = true;
break;
@ -383,3 +383,8 @@ bool TeensyFileManager::writeByte(int8_t fd, uint8_t v)
return true;
}
void TeensyFileManager::getRootPath(char *toWhere, int8_t maxLen)
{
strcpy(toWhere, "/A2DISKS/");
// strncpy(toWhere, "/A2DISKS/", maxLen);
}

View File

@ -26,6 +26,8 @@ class TeensyFileManager : public FileManager {
virtual uint8_t readByte(int8_t fd);
virtual bool writeByte(int8_t fd, uint8_t v);
virtual void getRootPath(char *toWhere, int8_t maxLen);
private:
bool _prepCache(int8_t fd);

View File

@ -7,11 +7,11 @@ const byte ROWS = 5;
const byte COLS = 13;
char keys[ROWS][COLS] = {
{ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', DEL },
{ ESC, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']' },
{ _CTRL, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', RET },
{ LSHFT, 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', RSHFT, 0 },
{ LOCK, '`', TAB, '\\', LA, ' ', RA, LARR, RARR, DARR, UARR, 0, 0 }
{ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', PK_DEL },
{ PK_ESC, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']' },
{ PK_CTRL, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', PK_RET },
{ PK_LSHFT, 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', PK_RSHFT, 0 },
{ PK_LOCK, '`', PK_TAB, '\\', PK_LA, ' ', PK_RA, PK_LARR, PK_RARR, PK_DARR, PK_UARR, 0, 0 }
};
uint8_t rowsPins[ROWS] = { 33, 34, 35, 36, 37 };
@ -62,29 +62,29 @@ void TeensyKeyboard::pressedKey(uint8_t key)
if (key & 0x80) {
// it's a modifier key.
switch (key) {
case _CTRL:
case PK_CTRL:
ctrlPressed = 1;
break;
case LSHFT:
case PK_LSHFT:
leftShiftPressed = 1;
break;
case RSHFT:
case PK_RSHFT:
rightShiftPressed = 1;
break;
case LOCK:
case PK_LOCK:
capsLock = !capsLock;
break;
case LA:
case PK_LA:
leftApplePressed = 1;
break;
case RA:
case PK_RA:
rightApplePressed = 1;
break;
}
return;
}
if (key == ' ' || key == DEL || key == ESC || key == RET || key == TAB) {
if (key == ' ' || key == PK_DEL || key == PK_ESC || key == PK_RET || key == PK_TAB) {
buffer.addByte(key);
return;
}
@ -151,19 +151,19 @@ void TeensyKeyboard::releasedKey(uint8_t key)
if (key & 0x80) {
// it's a modifier key.
switch (key) {
case _CTRL:
case PK_CTRL:
ctrlPressed = 0;
break;
case LSHFT:
case PK_LSHFT:
leftShiftPressed = 0;
break;
case RSHFT:
case PK_RSHFT:
rightShiftPressed = 0;
break;
case LA:
case PK_LA:
leftApplePressed = 0;
break;
case RA:
case PK_RA:
rightApplePressed = 0;
break;
}

View File

@ -12,8 +12,8 @@ class TeensyKeyboard : public PhysicalKeyboard {
virtual void maintainKeyboard();
// Interface used by the BIOS...
bool kbhit();
int8_t read();
virtual bool kbhit();
virtual int8_t read();
private:

View File

@ -12,9 +12,8 @@
#include "teensy-paddles.h"
#include "teensy-filemanager.h"
#include "appleui.h"
#define RESETPIN 39
#define BATTERYPIN A19
#define BATTERYPIN 32
#define SPEAKERPIN A21
#include "globals.h"
@ -25,20 +24,8 @@ uint32_t startMicros;
BIOS bios;
enum {
D_NONE = 0,
D_SHOWFPS = 1,
D_SHOWMEMFREE = 2,
D_SHOWPADDLES = 3,
D_SHOWPC = 4,
D_SHOWCYCLES = 5,
D_SHOWBATTERY = 6,
D_SHOWTIME = 7
};
uint8_t debugMode = D_NONE;
bool g_prioritizeDisplay = false; // prioritize real-time audio by default, not the display
#define SPEEDCTL 0.97751710654936461388 // that's how many microseconds per cycle @ 1.023 MHz
// How many microseconds per cycle
#define SPEEDCTL ((float)1000000/(float)g_speed)
static time_t getTeensy3Time() { return Teensy3Clock.get(); }
@ -139,8 +126,8 @@ void setup()
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/JORJ/disk_s6d1.dsk", false);
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/GAMES/ALIBABA.DSK", false);
pinMode(56, OUTPUT);
pinMode(57, OUTPUT);
// pinMode(56, OUTPUT);
// pinMode(57, OUTPUT);
Serial.print("Free RAM: ");
Serial.println(FreeRamEstimate());
@ -193,7 +180,7 @@ void biosInterrupt()
}
// if we turned off debugMode, make sure to clear the debugMsg
if (debugMode == D_NONE) {
if (g_debugMode == D_NONE) {
g_display->debugMsg("");
}
@ -216,8 +203,10 @@ void biosInterrupt()
//bool debugState = false;
//bool debugLCDState = false;
void runCPU()
{
g_inInterrupt = true;
// Debugging: to watch when the speaker is triggered...
// static bool debugState = false;
// debugState = !debugState;
@ -229,9 +218,9 @@ void runCPU()
// directly from within it, so it needs to be real-ish time.
if (micros() > nextInstructionMicros) {
// Debugging: to watch when the CPU is triggered...
static bool debugState = false;
debugState = !debugState;
digitalWrite(56, debugState);
// static bool debugState = false;
// debugState = !debugState;
// digitalWrite(56, debugState);
uint8_t executed = g_cpu->Run(24);
@ -242,6 +231,8 @@ void runCPU()
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
}
g_inInterrupt = false;
}
void loop()
@ -276,6 +267,7 @@ void loop()
// but the display tears. So there's a global - g_prioritizeDisplay -
// which lets the user pick which they want.
g_prioritizeDisplay = false;
if (g_prioritizeDisplay)
Timer1.stop();
g_vm->vmdisplay->lockDisplay();
@ -288,13 +280,13 @@ void loop()
if (g_prioritizeDisplay)
Timer1.start();
static unsigned long nextBattCheck = 0;
static unsigned long nextBattCheck = millis() + 30;// debugging
static int batteryLevel = 0; // static for debugging code! When done
// debugging, this can become a local
// in the appropriate block below
if (millis() >= nextBattCheck) {
// FIXME: what about rollover?
nextBattCheck = millis() + 3 * 1000; // check every 30 seconds
nextBattCheck = millis() + 3 * 1000; // check every 3 seconds
// This is a bit disruptive - but the external 3.3v will drop along with the battery level, so we should use the more stable (I hope) internal 1.7v.
// The alternative is to build a more stable buck/boost regulator for reference...
@ -316,7 +308,7 @@ void loop()
* 3.46v = 144 - 146
* 4.21v = 172
*/
#if 1
#if 0
Serial.print("battery: ");
Serial.println(batteryLevel);
#endif
@ -334,7 +326,7 @@ void loop()
void doDebugging()
{
char buf[25];
switch (debugMode) {
switch (g_debugMode) {
case D_SHOWFPS:
// display some FPS data
static uint32_t startAt = millis();

View File

@ -1,6 +1,8 @@
#ifndef __VMDISPLAY_H
#define __VMDISPLAY_H
#include <stdint.h>
class MMU;
typedef struct {

View File

@ -1,3 +1,7 @@
#ifdef TEENSYDUINO
#include <Arduino.h>
#endif
#include "vmram.h"
#include <string.h>
#include "globals.h"
@ -5,7 +9,8 @@
#ifndef TEENSYDUINO
#include <assert.h>
#else
#define assert(x)
#define assert(x) { if (!(x)) {Serial.print("assertion failed at "); Serial.println(__LINE__); delay(10000);} }
//#define assert(x) { }
#endif
// Serializing token for RAM data
@ -22,9 +27,15 @@ void VMRam::init()
}
}
uint8_t VMRam::readByte(uint32_t addr) { assert(addr < sizeof(preallocatedRam)); return preallocatedRam[addr]; }
uint8_t VMRam::readByte(uint32_t addr)
{
return preallocatedRam[addr];
}
void VMRam::writeByte(uint32_t addr, uint8_t value) { assert(addr < sizeof(preallocatedRam)); preallocatedRam[addr] = value; }
void VMRam::writeByte(uint32_t addr, uint8_t value)
{
preallocatedRam[addr] = value;
}
bool VMRam::Serialize(int8_t fd)
{
@ -73,3 +84,8 @@ bool VMRam::Deserialize(int8_t fd)
return true;
}
bool VMRam::Test()
{
return true;
}

26
vmram.h
View File

@ -18,8 +18,32 @@ class VMRam {
bool Serialize(int8_t fd);
bool Deserialize(int8_t fd);
bool Test();
private:
uint8_t preallocatedRam[591*256]; // 591 pages of RAM
// We need 591 pages of 256 bytes for the //e. There's not
// enough RAM in the Teensy 3.6 for both this (nearly 148k)
// and the display's DMA (320*240*2 = 150k).
//
// We could put all of the //e RAM in an external SRAM -- but the
// external SRAM access is necessarily slower than just reading the
// built-in RAM. So this is a hybrid: we allocate some internal
// SRAM from the Teensy, and will use it for the low addresses of
// our VM space; and anything above that goes to the external SRAM.
//
// Changing this invalidates the save files, so don't just change it
// willy-nilly :)
//
// Zero-page should be in internal RAM (it's changed very often). Some
// other pages that are read or written often should probably go in
// here too. The order of the pages (in apple/applemmu.cpp) defines
// what order the pages are referenced in the VMRam object; the lowest
// wind up in internal RAM.
// Pages 0-3 are ZP; we want those in RAM.
// Pages 4-7 are 0x200 - 0x3FF. We want those in RAM too (text pages).
uint8_t preallocatedRam[591*256];
};