mirror of https://github.com/JorjBauer/aiie.git
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:
parent
c25f6e757e
commit
99d0c8e72c
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
19
Makefile
19
Makefile
|
@ -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
|
||||
|
||||
|
|
|
@ -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 ###
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
136
apple/diskii.cpp
136
apple/diskii.cpp
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
#include "filemanager.h"
|
||||
#include "applemmu.h"
|
||||
#include "Slot.h"
|
||||
#include "slot.h"
|
||||
|
||||
#include "LRingBuffer.h"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#endif
|
||||
|
||||
#include "applemmu.h"
|
||||
#include "Slot.h"
|
||||
#include "slot.h"
|
||||
|
||||
class Fx80;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|