]> Shamusworld >> Repos - apple2/commitdiff
Added support for the Apple High Speed SCSI card.
authorShamus Hammons <jlhamm@acm.org>
Mon, 25 Feb 2019 22:34:27 +0000 (16:34 -0600)
committerShamus Hammons <jlhamm@acm.org>
Mon, 25 Feb 2019 22:34:27 +0000 (16:34 -0600)
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
apple2.cfg
src/apple2.cpp
src/apple2.h
src/floppydrive.cpp
src/harddrive.cpp
src/settings.cpp
src/settings.h
web/index.html

index efe1d56a18eeb4c510f73e46b4c5073bf2d6719b..6ff8ef88c7f5fd623b60a50669f00925b836048f 100644 (file)
@@ -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
index b4cac17fdeac00eccfd2a090f7e12f161bf2464c..e1e024c505ab6347f1e11eeb8dccf89808e30ea3 100644 (file)
@@ -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)
 
index 72e6f3c8f22705ada373fd55ee1c071ff341d5a5..ce38ed1721890d9de75bddf65e75adf3d95a550b 100644 (file)
@@ -46,7 +46,8 @@
 #include <string>
 #include <time.h>
 #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));
index 0bbf6d04a205dc5670a99d5305a41b78be063c3c..db9112a5c3fca0243876cc5d10de6196be74298c 100644 (file)
@@ -3,7 +3,6 @@
 //
 
 #include <stdint.h>
-#include "floppydrive.h"
 #include "v65c02.h"
 
 enum { APPLE_TYPE_II, APPLE_TYPE_IIE, APPLE_TYPE_IIC };
index 67fa079ba90bd40b41c1d29a845315d176f980af..ba9050a9b04a5ec820b727be8f6ece5924de6987 100644 (file)
@@ -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;
index 0e6a99487bf7900d88b1289603a13d97eea4182a..3011fad9c4bb387f19febf5081dd2e21d8a0b5b6 100644 (file)
 
 #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);
 }
 
index 3939649aa056382e22be6751b4c5ae361bd0352f..81b4d1df26f9742267f513df21b72f8fb1e9ada7 100644 (file)
@@ -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);
 }
index daad84d89cd0ce1c9321228e366046012a43e692..e97dcdf1de44666f959add5542b8b3b1ef15b99a 100644 (file)
@@ -46,6 +46,7 @@ struct Settings
 
        char BIOSPath[MAX_PATH];
        char disksPath[MAX_PATH];
+       char hdPath[MAX_PATH];
        char autoStatePath[MAX_PATH];
 };
 
index 5ebe534ce49434942b5b302b727a6128d518420e..9310b4634c64a229583d7c7eee67797da49f3aed 100644 (file)
@@ -82,7 +82,7 @@
 <li>F12: Toggle full screen on/off</li>
 <li>Pause: Pause/unpause the emulation</li>
 <li>Ctrl-Shift-Q: Quit Apple2</li>
-<li>Ctrl-~: Ctrl-RESET</li>
+<li>Ctrl-Home: Ctrl-RESET</li>
 <li>Left Alt: Open apple key</li>
 <li>Right Alt: Closed apple key</li>
 </ul>