From e3c7ee5d936cdddcc3afe4bf3f059ef0f3e28af2 Mon Sep 17 00:00:00 2001 From: Shamus Hammons Date: Mon, 25 Feb 2019 16:34:27 -0600 Subject: [PATCH] Added support for the Apple High Speed SCSI card. This means that you can now connect hard drive images to the emulator, and, with the addition of the AHSSC emulation, connect up to seven of them to one system (technically, with 8 LUNs per SCSI ID, you could in theory connect 56 of them, but that's just crazy). The emulation of the card is still in an early state, but it currently seems to work properly with the "Pitch Dark" hard drive image by 4am. Still needs some work to expose it properly to the GUI, but hey, that's just details. :-) --- .gitignore | 7 +- apple2.cfg | 1 + src/apple2.cpp | 4 +- src/apple2.h | 1 - src/floppydrive.cpp | 27 +- src/harddrive.cpp | 853 ++++++++++++++++++++++++++++++++++++++++++++ src/settings.cpp | 1 + src/settings.h | 1 + web/index.html | 2 +- 9 files changed, 870 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index efe1d56..6ff8ef8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ apple2 +apple2.exe *.log *.zip *.state* *.bak +*.woz disks/ gmon.out obj/ @@ -13,4 +15,7 @@ ROMs/bin2c ROMs/from-applewin/ res/ docs/ -wozmaker/ +web/source/ +ROMs/asimov/ +misc/ +changes-since-last-commit.txt diff --git a/apple2.cfg b/apple2.cfg index b4cac17..e1e024c 100644 --- a/apple2.cfg +++ b/apple2.cfg @@ -12,6 +12,7 @@ #ROMs = ./ROMs #default #disks = ./disks +#harddrive = ./disks/Pitch-Dark-20180731.2mg # Auto state loading/saving upon starting/quitting Apple2 (1 - use, 0 - don't use) diff --git a/src/apple2.cpp b/src/apple2.cpp index 72e6f3c..ce38ed1 100644 --- a/src/apple2.cpp +++ b/src/apple2.cpp @@ -46,7 +46,8 @@ #include #include #include "firmware.h" -//#include "floppydisk.h" +#include "floppydrive.h" +#include "harddrive.h" #include "log.h" #include "mmu.h" #include "mockingboard.h" @@ -546,6 +547,7 @@ int main(int /*argc*/, char * /*argv*/[]) // Install devices in slots InstallFloppy(SLOT6); InstallMockingboard(SLOT4); + InstallHardDrive(SLOT7); // Set up V65C02 execution context memset(&mainCPU, 0, sizeof(V65C02REGS)); diff --git a/src/apple2.h b/src/apple2.h index 0bbf6d0..db9112a 100644 --- a/src/apple2.h +++ b/src/apple2.h @@ -3,7 +3,6 @@ // #include -#include "floppydrive.h" #include "v65c02.h" enum { APPLE_TYPE_II, APPLE_TYPE_IIE, APPLE_TYPE_IIC }; diff --git a/src/floppydrive.cpp b/src/floppydrive.cpp index 67fa079..ba9050a 100644 --- a/src/floppydrive.cpp +++ b/src/floppydrive.cpp @@ -96,7 +96,6 @@ FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), i headPos[0] = headPos[1] = 0; trackLength[0] = trackLength[1] = 51200; disk[0] = disk[1] = NULL; -// woz[0] = woz[1] = NULL; diskSize[0] = diskSize[1] = 0; diskType[0] = diskType[1] = DT_EMPTY; imageDirty[0] = imageDirty[1] = false; @@ -511,7 +510,6 @@ void FloppyDrive::EjectImage(uint8_t driveNum/*= 0*/) free(disk[driveNum]); disk[driveNum] = NULL; -// woz[driveNum] = NULL; diskSize[driveNum] = 0; diskType[driveNum] = DT_EMPTY; imageDirty[driveNum] = false; @@ -756,7 +754,6 @@ If it ever *does* become a problem, doing the physical modeling of the head movi else phase[activeDrive] &= ~phaseBit; -#if 1 uint8_t oldHeadPos = headPos[activeDrive] & 0x07; int16_t newStep = step[phase[activeDrive]][oldHeadPos]; int16_t newHeadPos = (int16_t)headPos[activeDrive] + newStep; @@ -764,33 +761,17 @@ If it ever *does* become a problem, doing the physical modeling of the head movi // Sanity check if ((newHeadPos >= 0) && (newHeadPos <= 140)) headPos[activeDrive] = (uint8_t)newHeadPos; -#else - // See if the new phase solenoid is energized, & move the stepper/head - // appropriately. - // N.B.: The head stub is located by bits 1 & 2 of the headPos variable - uint8_t oldHeadPos = headPos[activeDrive]; - uint8_t nextUp = 1 << (((oldHeadPos >> 1) + 1) & 0x03); - uint8_t nextDown = 1 << (((oldHeadPos >> 1) - 1) & 0x03); - - // We simulate cogging here by seeing if there's a valid up and/or down - // position to go to. If both are valid, the head goes nowhere. - if (phase[activeDrive] & nextUp) - headPos[activeDrive] += (headPos[activeDrive] < 140 ? 2 : 0); - - if (phase[activeDrive] & nextDown) - headPos[activeDrive] -= (headPos[activeDrive] > 0 ? 2 : 0); -#endif if (oldHeadPos != headPos[activeDrive]) { WOZ2 & woz = *((WOZ2 *)disk[activeDrive]); uint8_t newTIdx = woz.tmap[headPos[activeDrive]]; - float newBitLen = (newTIdx == 0xFF ? 51200.0f - : Uint16LE(woz.track[newTIdx].bitCount)); + float newBitLen = (newTIdx == 0xFF + ? 51200.0f : Uint16LE(woz.track[newTIdx].bitCount)); uint8_t oldTIdx = woz.tmap[oldHeadPos]; - float oldBitLen = (oldTIdx == 0xFF ? 51200.0f - : Uint16LE(woz.track[oldTIdx].bitCount)); + float oldBitLen = (oldTIdx == 0xFF + ? 51200.0f : Uint16LE(woz.track[oldTIdx].bitCount)); currentPos[activeDrive] = (uint32_t)((float)currentPos[activeDrive] * (newBitLen / oldBitLen)); trackLength[activeDrive] = (uint16_t)newBitLen; diff --git a/src/harddrive.cpp b/src/harddrive.cpp index 0e6a994..3011fad 100644 --- a/src/harddrive.cpp +++ b/src/harddrive.cpp @@ -15,11 +15,864 @@ #include "harddrive.h" #include "apple2.h" +#include "dis65c02.h" +#include "fileio.h" #include "firmware.h" +#include "log.h" #include "mmu.h" +#include "settings.h" +#include "v65c02.h" // For dumpDis... + + +static uint8_t bank = 0; +static uint8_t ramBank = 0; +static uint8_t deviceID = 7; +static bool dmaSwitch = false; +static uint8_t staticRAM[0x2000] = { 0 }; +//static char buffer[2048]; +static uint8_t reg[16]; + +// Stuff that will have to GTFO of here +static uint8_t * hdData = NULL;//[(0x10000 * 512) + 0x40]; + + +/* +$2 clears bit 1 and puts it back +$C clears bit 0 & 1 and puts it back +$F sets bit 7 and puts it back +reads $4, if 0 or <= 4 after anding with $BE, CLC & RTS + else, put $81 into $C88F, else SEC & RTS (obv. failure mode) +$3 is cleared before going to $CF2F + which sets, clears, then sets again bit 7 of $E + +$C bits: + 0: + 1: + 2: + 3: + 4: + 5: + 6: Physical DMA switch on card + 7: + +$F bits: + 0-2: RAM bank # (?) + 3: Enable RAM bank in bits 0-2 (or make writable maybe?) + 4-7: ??? + +Switches on the card: +#1 sets DMA on/off (switch pos UP = OPEN = off) +#2-4 sets the computer's SCSI ID number (preset at factor to 7) + +Looks like bits 5-7 of register $E is device ID + +From Apple II SCSI Card Tech. Ref.: + +$0 R Current SCSI data register +$0 W Output data register +$1 R/W Initiator command register +$2 R/W Mode Select register +$3 R/W Target command register +$4 R SCSI bus status +$4 W Select enable register +$5 R Bus and Status register +$6 R Input data register +$7 R Reset parity/interrupts + +$8 R/W PDMA/DACK +$9 R SCSI device ID +$A W Memory Bank Select register +$B W Reset 5380 IC +$D W PDMA mode enable +$E R Read DRQ status bit through D7 bit + +N.B.: The A2 HS SCSI card wires the A0-A2 lines backwards. So it maps like so: (No, it must be a mistake on the schematic as the code doesn't line up with that interpretation) + +ZP locations: +$42 Command number +$43 Unit number +$44-45 Buffer pointer +$46-47 Block number + +0123456789ABCDEF +@ABCDEFGHIJKLMNO +PQRSTUVWXYZ _ + +So the path of execution is: + + $CC00 is written to, is that the bank select writable flag (@ reg. $E)? + $CD00 hide bank select? + $CD01 restore bank select? + $C808 gets slot # (+$20) + $C809 gets 0 + - Bank 11:0 + $C80B gets set with $98 to signal we've been there already + $5D gets flags (6 = running on GS, 5 = bit 6 of reg. $C is set) + [could it be that bit 6 of $C is physical DMA enable switch?] + $5E is the slot # (+$20) + $C80C gets the contents of $5D + $C893 gets set with $80 (signal we're in I set mode) + $C896 is set with GS Speed Register (0 on non-GS models) + $C807 gets set with the SP + execution then jumps to... + - Bank 15:0 + $C809 gets $40 (& $BF32 as well!) + $C80A gets 0 + Calls bank 3:0 + - Bank 3:0 (Look for bootable drive) + $C883 gets 0 + $C815 gets 0 + $C80D gets 0 (# of drives found?) + $C80F gets 0 + $C8DA gets: Device ID from $E is massaged and changed into a single bit + Calls bank 21:3 + - Bank 21:3 + Stores $80 (RST) in reg. $1, burns some cycles, stores 0 in reg. $1 + [Looks like ASSERT /RST] + burn cycles, but burn most if $C8DA == 4 + Clears 32 bytes @ $C92F + $C817 gets $40 |_________________ + $C818 gets 0 | Failure countdown + $C8DB gets SCSI ID # from loop (bit field) + $4F gets cleared (error flag) + Calls $CF5F (send command to device?) + So the buffer (@ $C923) looks like so before the call: +* 00 00 00 00 00 00 .. .. .. .. .. .. C3 C9 00 + ^$60/1 points here ^$56/7 points here ($62 = $58 = 0) +* Puts $C9C3 into $C92F/30, zeroes $C931 + Then calls bank 16:0 + - Bank 16:0 + Stores to $CD00 + Calls $CDD0 + Clears bit 1 from reg. $2, bits 0 & 1 from $C + [Looks like it clears the DMA MODE bit] +* Clears $4F, $C806, $C88F, $C890, $C8EE-F0 + Sets bit 7 of reg. $F + Calls $CECE + Gets reg. $4, checks for 0, returns success if so + [R is SCSI Bus Status] + Masks bits 1-5 & 7, checks for 2 or 4, returns success if so + [bit 2 = /I/O, bit 1 = /SEL] + Else, $81 -> $C88F, returns failure (set bit 7 of $C806, sets C) + Calls $CF42 + Returns since $C893 has $80 in it + Calls $CC24 (Arbitrate phase) + Zeroes reg. $3 + [Target Command, set Data Out] + Toggles bit 7 of reg. $E (ON-off-ON) + Puts host ID(?) in reg. $0 + [W: Output Data - sends data on SCSI bus] + Loop: + Puts 0 in reg. $2, then sets bit 0 of reg. $2 + [bit 0 is ARBITRATE, requires SCSI device ID in $0] + Gets reg. $C, checks bit 4 + If clear, then toggle reg. $E (ON-off-ON) & count down to failure + Check bit 6 of reg. $1, loop back if not set + [Initiator Command. bit 6 AIP, if set bus free detected] + Check bit 5 of reg. $1, loop back if not clear + [Initiator Command, bit 5 LA, if set, bus was lost] + Check reg. $0 to see if it's same as what's in $C8DA + [R: Current SCSI Data] + If not, see if it's >= to EORed value & loop back if it is + Checks bit 5 of reg. $1, loop back if not clear + [Initiator Command, bit 5 LA, if set, bus was lost] + Sets bits 1 & 2 of reg. $1, clear bits 5 & 6 of same + [Initiator Command: 1 = ASSERT /ATN, 2 = ASSERT /SEL, clear AIP, LA] + Clear C and return if success, set $C88F to $80 & set C if failure + Calls $CC7A if succesful: + Zeroes out reg. $4 + [Select Enable: disable interrupts] + Stores $C8DA ORed with $C8DB into reg. $0 + [W: Output Data - writing ?] + Set bits 0 & 6 in reg. $1, clear 5 & 6 in reg. $1 + [W: 0- ASSERT DATA BUS, 6- TEST MODE; 5- unused(?), 6- TEST MODE off] + Clear bit 0 in reg. $2 + [W: Clear ARBITRATE] + Puts contents of $C8DC +set bit 7 into $C821 + Clears bit 3 in reg. $1 + [W: 3- ASSERT /BSY (0 disconnects from bus)] + Calls $CD51 + Toggle bit 7 of reg. $E (ON-off-ON) + Wait for bit 6 of reg. $4 to come on, if not, set C (signal failure) + [R: bit 6- /BSY] + Clears bit 2 in reg. $1 + [W: ASSERT /SEL (0 de-asserts)] + Clears bits 1, 5, 6 in reg. $1 + [W: 1- /ATN, 5- unused(?), 6- TEST MODE] + Clears bit 0 in reg. $1 + [W: ASSERT DATA BUS (0 de-asserts)] + Signals success (C = 0) or failure (C = 1, $C88F = $81) + Calls $CF58 + Returns since $C893 has $80 in it still + Calls $CCE4 + Checks if bit 4 of reg. $C is set, if not, toggle bit 7 of reg. $E + Checks $4, if either of bits 1 & 6 are set, if not, signal failure + If only bit 2 or 2 & 6 is set, loop back to beginning of call + Clears bit 1 of reg. $2, then restores it to what it was + [W: 1- DMA MODE] + Checks for bit 5 of reg. $4, if not set, loop back to begin + [R: 5- /REQ] + Moves $C81F into $C820 + Restores reg. $4 from Y, masks off bits 2-4 and puts it in $C81F + [R: 4- /MSG, 3- /C/D, 2- /I/O] + Puts prev. value r. shifted 1 into $C82B + Uses that as index into jump table + R. shifts again by 1 and stuffs into reg. $3 + [W: Target Command- writes /MSG, /C/D, /I/O] + Calls $CD48 + Using Y as index, push value pair @ $CFB4 onto stack & return to call + Calls a routine from 0-7... + 0-1 goes to $6E6C or copies $56-8 into $C81C-E, calls bank 18:0 + - Bank 18:0 + ... + Calls bank 20:0 or 1 (0 for read, 1 for write--PIO mode) + 2 calls bank 17:0 (/C/D) + 3 calls bank 17:3 (/C/D + /I/O) + 4-5 signals failure & returns (bit 4: /MSG, no /C/D = failure) + 6 calls bank 17:2 (/MSG + /C/D) + [During init, it comes here...] + Gets $C821, compares it to 1, if so, signal failure & return + Calls $CE79 + a + 7 calls bank 17:1 (/MSG + /C/D + /I/O) + If bit 7 of $C806 is clear, loop back to begin + Calls $CDA0 + Does some error checking on $C88F and $C8EC + Jumps to $CE18 + Clears bit 1 from reg. $2, bits 0-1 from reg. $C + Moves $C88F into $4F + If it's 0 or $8E, or reg. $4 is 0, skip over next + Calls $CE6C + Moves $C88F into $4F + Zeroes out regs. $1, $2, $3, $C + Stores to $CD01 + [Returns to $CC6D in bank 3:0] + Calls $CC9F (Function 1 - INQUIRY + more + Zeroes $C8CF, $C892 + Calls $CD0E + [12 00 00 00 1E 00 .. .. .. .. .. .. C3 C9 00 .. 1E] + Calls bank 16:0 (Do INQUIRY) + if $C9C3 (1st byte of INQUIRY data) == $10 + $C892 <- $80 + $C8CB <- $06 + $C8B9 <- $F8 + $C8CC <- $C0 + else if == 2 or 6, + $C892 <- $40 + $C8CF |= $0C + else (depending on 1st byte), + (5=CDROM, 6=DA Tape drive, 7=HD, 8=Scanner, 9=Printer, 3=nonspecific) + $C8CB <- 07 06 09 FF FF 05 08 + $C8B9 <- C0 C0 A0 00 00 C0 A0 + $C8CC <- F8 F8 78 FF FF B4 70 + Sets bit 5, clears bit 6 in $C8CC + if bits 4-5, 7 are set, set bit 0 of $C8CF + Copies 16 bytes of returned data from $C9C3 + $17 to $C8BB + $C8CE <- $30 + $C8CD <- $00 + Calls bank 21:1 (lock CD-ROM?) + Does PREVENT ALLOW MEDIUM REMOVAL if $C8CB == 5 + $C927 <- $01 + Calls $CDDD (MODE SENSE/MODE SELECT) + Calls $CEA8 (READ CAPACITY) + Calls 16:0 with command READ CAPACITY, data returned @ $C9C3 (8 bytes) + $C8AB <- $C9C6 + $C8AA <- $C9C5 + $C8A9 <- $C9C4 + if $C8A9 =! 0, set bit 0 of $C8CF + $C8A8 <- $C9C3 + if $C8A8 != 0, set bit 0 of $C8CF + $C8AF <- $C9C7, save flags + $C8AE <- $C9C8, save flags + $C8AD <- $C9C9, save in Y + $C8AC <- $C9CA + Zeroes error flag ($4F) + $C8CF |= 0x0C + Calls $CFC7 (bank 4:0 direct) + - Bank 4:0 + Calls $CD65 (READ--reads 512 bytes from LBA set from $C8D2 + 1) + Calls $CDDA, sets carry if 1st two bytes are not 'PM' + Calls $CD39 + Calls $CD1A + $C8D0 <- $C80F + Calls $CDF1 + [Returns to $CCDB in bank 3:0] + Loops back if bit 7 of $C892 is clear + Calls $CD05 (bank 21:2 direct--unlock CD-ROM?) + Adds 1 to $C8DC + Loops back if $C8DC != 8 + ... + [Returns to $CC10 in bank 15:0] + Checks if call was successful (if not jumps to bank 11:1) + execution jumps to... + - Bank 11:2 ($CD9A) + Puts 1 in $43 (unit #), $44 + Zeroes out $46-49 (block # [2], ??? [2]) + Puts $08 in $41, zeroes out $40, $42 (command) + Calls bank 9:0 + - Bank 9:0 + ... + Calls bank 16:0 + +SCSI Phases +----------- + +Selection: In this state, the initiator selects a target unit and get the target to carry out a given function, such as reading or writing data. The initiator outpus the OR value of its SCSI-ID and the SCSI-ID of the target onto the data bus (for example, if the initiator is 2 and the target is 5 then the OR-ed ID on the bus will be 00100100). The target then determines that its ID is on the data bus and sets the /BSY line active. If this does not happen within a given time, then the initiator deactivates the /SEL signal, and the bus will be free. The target determines that it is selected when the /SEL signal and its SCSI ID bit are active and the /BSY and /I/O signals are false. It then asserts the signal within a selection abort time (200µs). +*/ +static bool DATA_BUS = false; +static bool DMA_MODE = false; +static bool BSY = false; +static bool ATN = false; +static bool SEL = false; +static bool ACK = false; +static bool RST = false; +static bool MSG = false; +static bool C_D = false; +static bool I_O = false; +static bool REQ = false; +static bool DEV_BSY = false; +static bool DRQ = false; +static bool DACK = false; +static uint8_t devMode = 8; +static uint8_t cmdLength; +static uint8_t cmd[256]; +static uint32_t bytesToSend; +static uint8_t * buf; +static uint32_t bufPtr; + + +static void RunDevice(void) +{ +//WriteLog(" >>> RUNNING HD...\n"); + // Let's see where it's really going... +/* if (mainCPU.pc == 0xCE7E) + dumpDis = true;//*/ + + static uint8_t readCapacity[8] = { 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00 }; + static uint8_t inquireData[30] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'S', 'E', 'A', 'G', 'A', 'T', 'E', ' ', 'P', 'h', 'o', 'n', 'y', '1' }; + + enum { + DVM_DATA_OUT = 0, DVM_DATA_IN = 1, DVM_COMMAND = 2, DVM_STATUS = 3, + DVM_MESSAGE_OUT = 6, DVM_MESSAGE_IN = 7, DVM_BUS_FREE, DVM_ARBITRATE, + DVM_SELECT + }; + + if (RST) + { +WriteLog(" >>> DEVICE RESET...\n"); + devMode = DVM_BUS_FREE; + DEV_BSY = false; + return; + } + + switch (devMode) + { + case DVM_BUS_FREE: + if (SEL)//(BSY && SEL) + devMode = DVM_ARBITRATE; + + break; + case DVM_ARBITRATE: +//WriteLog(" >>> ARBITRATE PHASE (BSY=%i SEL=%i DATA_BUS=%i [%02X])\n", BSY, SEL, DATA_BUS, reg[0]); + if (!BSY && SEL && DATA_BUS && (reg[0] & 0x40)) + devMode = DVM_SELECT, DEV_BSY = true; + else if (!BSY && !SEL) + devMode = DVM_BUS_FREE; + + break; + case DVM_SELECT: +WriteLog(" >>> SELECT PHASE\n"); + if (ATN) + { + MSG = true, C_D = true, I_O = false; + devMode = DVM_MESSAGE_OUT; + REQ = true; + } + else + { + // If no ATN is asserted, go to COMMAND I guess? + // Let's try it +// errrr, no. this does not work. Or does it??? + MSG = false, C_D = true, I_O = false; + devMode = DVM_COMMAND; + cmdLength = 0; + } + + break; + case DVM_DATA_OUT: +WriteLog(" >>> DATA OUT PHASE\n"); + if (!ACK) + REQ = true; + + if (DMA_MODE) + { + if (!DACK) + { + // We just send zeroes for now... + reg[6] = 0; + DRQ = true; + } + else if (DRQ && DACK) + { + DRQ = false; + DACK = false; + bytesToSend--; + + if (bytesToSend == 0) + { + REQ = false; + MSG = false, C_D = true, I_O = true; + devMode = DVM_STATUS; + } + } + } + + break; + case DVM_DATA_IN: +WriteLog(" >>> DATA IN PHASE (bts=%u)\n", bytesToSend); + if (!ACK) + REQ = true; + + if (DMA_MODE) + { + if (!DACK) + { + // We just send zeroes for now... + if (buf == NULL) + reg[6] = 0; + else + reg[6] = buf[bufPtr]; + + DRQ = true; + } + else if (DRQ && DACK) + { + DRQ = false; + DACK = false; + bytesToSend--; + bufPtr++; + + if (bytesToSend == 0) + { + REQ = false; + MSG = false, C_D = true, I_O = true; + devMode = DVM_STATUS; + buf = NULL; + } + } + } + + break; + case DVM_COMMAND: +WriteLog(" >>> COMMAND PHASE\n"); + if (!ACK) + REQ = true; + else if (REQ && ACK) + { + cmd[cmdLength++] = reg[0]; + WriteLog("HD: Write to target value $%02X\n", reg[0]); + REQ = false; + } + + // Handle "Test Unit Ready" command + if ((cmd[0] == 0) && (cmdLength == 6)) + { + WriteLog("HD: Received command TEST UNIT READY\n"); + REQ = false; + // Drive next phase + MSG = false, C_D = true, I_O = true; + devMode = DVM_STATUS; + } + // Handle "Request Sense" command + else if ((cmd[0] == 0x03) && (cmdLength == 6)) + { + WriteLog("HD: Received command REQUEST SENSE [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]); + REQ = false; + // Drive next phase + MSG = false, C_D = false, I_O = true; + devMode = DVM_DATA_IN; + bytesToSend = cmd[4]; + } + // Handle "Read" (6) command + else if ((cmd[0] == 0x08) && (cmdLength == 6)) + { + WriteLog("HD: Received command READ(6) [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]); + REQ = false; + // Drive next phase + MSG = false, C_D = false, I_O = true; + devMode = DVM_DATA_IN; + bytesToSend = cmd[4] * 512; // amount is set in blocks + } + // Handle "Inquire" command + else if ((cmd[0] == 0x12) && (cmdLength == 6)) + { + WriteLog("HD: Received command INQUIRE [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]); + REQ = false; + // Drive next phase + MSG = false, C_D = false, I_O = true; + devMode = DVM_DATA_IN; + bytesToSend = cmd[4]; + buf = inquireData; + bufPtr = 0; + } + // Handle "Mode Select" command + else if ((cmd[0] == 0x15) && (cmdLength == 6)) + { + WriteLog("HD: Received command MODE SELECT [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]); + REQ = false; + // Drive next phase + MSG = false, C_D = false, I_O = false; + devMode = DVM_DATA_OUT; + bytesToSend = cmd[4]; + } + // Handle "Mode Sense" command + else if ((cmd[0] == 0x1A) && (cmdLength == 6)) + { + WriteLog("HD: Received command MODE SENSE [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]); + REQ = false; + // Drive next phase + MSG = false, C_D = false, I_O = true; + devMode = DVM_DATA_IN; + bytesToSend = cmd[4]; + } + // Handle "Read Capacity" command + else if ((cmd[0] == 0x25) && (cmdLength == 10)) + { + WriteLog("HD: Received command READ CAPACITY [%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], cmd[9]); + REQ = false; + // Drive next phase + MSG = false, C_D = false, I_O = true; + devMode = DVM_DATA_IN; + bytesToSend = 8;//cmd[4]; + buf = readCapacity; + bufPtr = 0; + } + // Handle "Read" (10) command + else if ((cmd[0] == 0x28) && (cmdLength == 10)) + { + WriteLog("HD: Received command READ(10) [%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], cmd[9]); + REQ = false; + // Drive next phase + MSG = false, C_D = false, I_O = true; + devMode = DVM_DATA_IN; + bytesToSend = ((cmd[7] << 8) | cmd[8]) * 512; // amount is set in blocks + uint32_t lba = (cmd[2] << 24) | (cmd[3] << 16) | (cmd[4] << 8) | cmd[5]; + buf = &hdData[(lba * 512) + 0x40]; + bufPtr = 0; + } + else if ((cmdLength == 6) && ((cmd[0] & 0xE0) == 0)) + { + WriteLog("HD: Received unhandled 6 command [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]); + } + else if ((cmdLength == 10) && (((cmd[0] & 0xE0) == 0x20) || ((cmd[0] & 0xE0) == 0x40))) + { + WriteLog("HD: Received unhandled 10 command [%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], cmd[9]); + } + + break; + case DVM_STATUS: +WriteLog(" >>> STATUS PHASE\n"); + if (!ACK) + { + // Return A-OK for everything for now... + reg[0] = 0; + REQ = true; + } + else if (REQ && ACK) + { + REQ = false; + // Drive next phase + MSG = true, C_D = true, I_O = true; + devMode = DVM_MESSAGE_IN; + } + + break; + case DVM_MESSAGE_OUT: +WriteLog(" >>> MESSAGE OUT PHASE\n"); + if (REQ && ACK) + { + uint8_t msg = reg[0]; + WriteLog("HD: Write to target value $%02X\n", msg); + REQ = false; + // Drive next phase + MSG = false, C_D = true, I_O = false; + devMode = DVM_COMMAND; + cmdLength = 0; + } + + break; + case DVM_MESSAGE_IN: +WriteLog(" >>> MESSAGE IN PHASE\n"); + if (!ACK) + { + // Return A-OK for everything for now... + reg[0] = 0; + REQ = true; + } + else if (REQ && ACK) + { + REQ = false; + // Drive next phase + MSG = false, C_D = false, I_O = false; + DEV_BSY = false; + devMode = DVM_BUS_FREE; + } + + break; + } +} + + +static uint8_t SlotIOR(uint16_t address) +{ + // This should prolly go somewhere else... + RunDevice(); + + char SCSIName[16][256] = { + "(RO) Current SCSI Data", + "Initiator Command", + "Mode", + "Target Command", + "(RO) Current SCSI Bus Status", + "(RO) Bus and Status", + "(RO) Input Data", + "(RO) Reset Parity/Interrupt", + "DMA Address LO", + "DMA Address HI", + "DMA Count LO", + "DMA Count HI", + "$C", + "$D", + "Bank/SCSI ID", + "$F" + }; + + uint8_t response = reg[address & 0x0F]; + + switch (address & 0x0F) + { + case 0x00: + // (RO) Current SCSI Data register + break; + case 0x01: + // Initiator Command register. Bits, from hi to lo: + // ASS. /RST, AIP, LA, ASS. /ACK, A./BSY, A./SEL, A./ATN, DATA BUS + + // Simulate ARBITRATE signal + if (reg[2] & 0x01) + response |= 0x40; + + break; + case 0x02: + // Mode register (chip control) + break; + case 0x03: + // Target Command register (SCSI bus info xfer phase) + break; + case 0x04: + // (RO) Current SCSI Bus Status register: Bits from hi to lo: + // /RST, /BSY, /REQ, /MSG, /C/D, /I/O, /SEL, /DBP +if (((mainCPU.pc != 0xCD7C) && (mainCPU.pc != 0xCD5F)) || (bank != 16)) + WriteLog(" [%02X %02X %02X %02X %02X %02X %02X %02X] [$C81F=$%02X $C80D=$%02X $C80A=$%02X $C887=$%02X $C806=$%02X $C88F=$%02X $C8EC=$%02X $4F=$%02X]\n", reg[0], reg[1], reg[2], reg[3], reg[4], reg[5], reg[6], reg[7], staticRAM[0x1F], staticRAM[0x0D], staticRAM[0x0A], staticRAM[0x87], staticRAM[0x06], staticRAM[0x8F], staticRAM[0xEC], ram[0x4F]); + + response = (RST ? 0x80 : 0) | (BSY | DEV_BSY ? 0x40 : 0) | (REQ ? 0x20 : 0) | (MSG ? 0x10 : 0) | (C_D ? 0x08 : 0) | (I_O ? 0x04 : 0) | (SEL ? 0x02 : 0); + break; + case 0x05: + { + // (RO) Bus and Status register + response = (ACK ? 0x01 : 0) | (ATN ? 0x02 : 0) | (DRQ ? 0x40 : 0); + uint8_t tgtMode = (MSG ? 0x04 : 0) | (C_D ? 0x02 : 0) | (I_O ? 0x01 : 0); + + if ((reg[3] & 0x07) == tgtMode) + response |= 0x08; + + break; + } + case 0x06: + // (RO) Input Data register (read from from SCSI bus) + if (DRQ) + DACK = true; + + break; + case 0x07: + // (RO) Reset Parity/Interrupt + // Resets PARITY ERR (bit 6), IRQ (bit 5), BUSY ERROR (bit 3) in + // register 5 (Bus & Status) + break; + case 0x0C: + response = 0x10 | (dmaSwitch ? 0x40 : 0); + break; + case 0x0E: + response = bank | (deviceID << 5); + break; + } + + if (((mainCPU.pc != 0xCD7C) && (mainCPU.pc != 0xCD5F)) || (bank != 16)) + WriteLog("HD Slot I/O read %s ($%02X <- $%X, PC=%04X:%u)\n", SCSIName[address & 0x0F], response, address & 0x0F, mainCPU.pc, bank); + + return response; +} + + +static void SlotIOW(uint16_t address, uint8_t byte) +{ + char SCSIName[16][256] = { + "(WO) Output Data", + "Initiator Command", + "Mode", + "Target Command", + "(WO) Select Enable", + "(WO) Start DMA Send", + "(WO) Start DMA Target Receive", + "(WO) Start DMA Initiator Receive", + "DMA Address LO", + "DMA Address HI", + "DMA Count LO", + "DMA Count HI", + "$C", + "$D", + "Bank/SCSI ID", + "$F" + }; + + switch (address & 0x0F) + { + case 0x00: + // (WO) Output Data register (data sent over SCSI bus) + if (DRQ) + DACK = true; + + break; + case 0x01: + // Initiator Command register. Bits, from hi to lo: + // ASS. /RST, AIP, LA, ASS. /ACK, A./BSY, A./SEL, A./ATN, DATA BUS + DATA_BUS = (byte & 0x01 ? true : false); + ATN = (byte & 0x02 ? true : false); + SEL = (byte & 0x04 ? true : false); + BSY = (byte & 0x08 ? true : false); + ACK = (byte & 0x10 ? true : false); + RST = (byte & 0x80 ? true : false); + break; + case 0x02: + // Mode register (chip control) + + // Dma ReQuest is reset here (as well as by hitting a pin) +// if ((byte & 0x02) == 0) +// DRQ = false; + DMA_MODE = (byte & 0x02 ? true : false); + + if (!DMA_MODE) + DRQ = DACK = false; + + break; + case 0x03: + // Target Command register (SCSI bus info xfer phase) + break; + case 0x04: + // (WO) Select Enable register + break; + case 0x05: + // (WO) Start DMA Send (initiates DMA send) + DRQ = true; + break; + case 0x06: + // (WO) Start DMA Target Receive (initiate DMA receive--tgt mode) + DRQ = true; + break; + case 0x07: + // (WO) Start DMA Initiator Receive (initiate DMA receive--ini mode) + DRQ = true; + break; + case 0x08: + // Lo byte of DMA address? + break; + case 0x09: + // Hi byte of DMA address? + break; + case 0x0A: + // 2's complement of lo byte of transfer amount? + break; + case 0x0B: + // 2's complement of hi byte of transfer amount? + break; + case 0x0C: + // Control/status register? + break; + case 0x0D: + // ??? + break; + case 0x0E: + // Bottom 5 bits of $E set the ROM bank... + bank = byte & 0x1F; +// WriteLog("HD: Setting bank %u\n", bank); + break; + case 0x0F: + // ??? RAM bank? Seems to be + ramBank = byte & 0x07; + break; + } + + WriteLog("HD Slot I/O write %s ($%02X -> $%X, PC=%04X:%u)\n", SCSIName[address & 0x0F], byte, address & 0x0F, mainCPU.pc, bank); + reg[address & 0x0F] = byte; + + if ((address & 0x0F) == 0x0E) + { + if (mainCPU.pc == 0xC78B) + { + uint16_t sp = mainCPU.sp; + uint16_t pc = ram[0x100 + sp + 1] | (ram[0x100 + sp + 2] << 8); + WriteLog(" *** Returning to bank %u, $%04X\n", bank, pc + 1); + } + else if (mainCPU.pc == 0xC768) + { + WriteLog(" *** Calling to bank %u:%u\n", mainCPU.a, (mainCPU.y & 0xE0) >> 5); + } + + WriteLog(" [%02X %02X %02X %02X %02X %02X %02X %02X] [$C81F=$%02X $C80D=$%02X $C80A=$%02X $C887=$%02X $C806=$%02X $C88F=$%02X $C8EC=$%02X $4F=$%02X]\n", reg[0], reg[1], reg[2], reg[3], reg[4], reg[5], reg[6], reg[7], staticRAM[0x1F], staticRAM[0x0D], staticRAM[0x0A], staticRAM[0x87], staticRAM[0x06], staticRAM[0x8F], staticRAM[0xEC], ram[0x4F]); + } + + // This should prolly go somewhere else... + RunDevice(); +} + + +static uint8_t SlotROM(uint16_t address) +{ + return hd2ROM[address]; +} + + +static uint8_t SlotIOExtraR(uint16_t address) +{ + if (address < 0x400) + return staticRAM[(ramBank * 0x400) + address]; + else + return hd2ROM[(bank * 0x400) + address - 0x400]; +} + + +static void SlotIOExtraW(uint16_t address, uint8_t byte) +{ + if (address < 0x400) + staticRAM[(ramBank * 0x400) + address] = byte; + else + { + WriteLog("Unhandled HD 1K ROM write ($%02X) @ $C%03X...\n", byte, address + 0x800); + + if ((mainCPU.pc == 0xCDDD) && (bank == 11)) + dumpDis = true; + } +} void InstallHardDrive(uint8_t slot) { + SlotData hd = { SlotIOR, SlotIOW, SlotROM, 0, SlotIOExtraR, SlotIOExtraW }; + InstallSlotHandler(slot, &hd); + + uint32_t size = 0; + hdData = ReadFile(settings.hdPath, &size); + + WriteLog("Read Hard Drive image file, %u bytes ($%X)\n", size, size); } diff --git a/src/settings.cpp b/src/settings.cpp index 3939649..81b4d1d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -104,6 +104,7 @@ void LoadSettings(void) strcpy(settings.BIOSPath, sdlemu_getval_string("BIOSROM", "./ROMs/apple2e-enhanced.rom")); strcpy(settings.disksPath, sdlemu_getval_string("disks", "./disks")); + strcpy(settings.hdPath, sdlemu_getval_string("harddrive", "./disks/Pitch-Dark-20180731.2mg")); strcpy(settings.autoStatePath, sdlemu_getval_string("autoStateFilename", "./apple2auto.state")); CheckForTrailingSlash(settings.disksPath); } diff --git a/src/settings.h b/src/settings.h index daad84d..e97dcdf 100644 --- a/src/settings.h +++ b/src/settings.h @@ -46,6 +46,7 @@ struct Settings char BIOSPath[MAX_PATH]; char disksPath[MAX_PATH]; + char hdPath[MAX_PATH]; char autoStatePath[MAX_PATH]; }; diff --git a/web/index.html b/web/index.html index 5ebe534..9310b46 100644 --- a/web/index.html +++ b/web/index.html @@ -82,7 +82,7 @@
  • F12: Toggle full screen on/off
  • Pause: Pause/unpause the emulation
  • Ctrl-Shift-Q: Quit Apple2
  • -
  • Ctrl-~: Ctrl-RESET
  • +
  • Ctrl-Home: Ctrl-RESET
  • Left Alt: Open apple key
  • Right Alt: Closed apple key
  • -- 2.37.2