]> Shamusworld >> Repos - apple2/blob - src/harddrive.cpp
3011fad9c4bb387f19febf5081dd2e21d8a0b5b6
[apple2] / src / harddrive.cpp
1 //
2 // Hard drive support
3 //
4 // by James Hammons
5 // (C) 2019 Underground Software
6 //
7 // This is done by emulating the Apple 2 High-Speed SCSI card.
8 //
9 // How it works:
10 //
11 // First 1K is the driver ROM, repeated four times.  After that, there are 31 1K
12 // chunks that are addressed in the $CC00-$CFFF address range; $C800-$CBFF is a
13 // 1K RAM space (internally, it's an 8K static RAM).
14 //
15
16 #include "harddrive.h"
17 #include "apple2.h"
18 #include "dis65c02.h"
19 #include "fileio.h"
20 #include "firmware.h"
21 #include "log.h"
22 #include "mmu.h"
23 #include "settings.h"
24 #include "v65c02.h"             // For dumpDis...
25
26
27 static uint8_t bank = 0;
28 static uint8_t ramBank = 0;
29 static uint8_t deviceID = 7;
30 static bool dmaSwitch = false;
31 static uint8_t staticRAM[0x2000] = { 0 };
32 //static char buffer[2048];
33 static uint8_t reg[16];
34
35 // Stuff that will have to GTFO of here
36 static uint8_t * hdData = NULL;//[(0x10000 * 512) + 0x40];
37
38
39 /*
40 $2 clears bit 1 and puts it back
41 $C clears bit 0 & 1 and puts it back
42 $F sets bit 7 and puts it back
43 reads $4, if 0 or <= 4 after anding with $BE, CLC & RTS
44     else, put $81 into $C88F, else SEC & RTS (obv. failure mode)
45 $3 is cleared before going to $CF2F
46     which sets, clears, then sets again bit 7 of $E
47
48 $C bits:
49    0:
50    1:
51    2:
52    3:
53    4:
54    5:
55    6: Physical DMA switch on card
56    7:
57
58 $F bits:
59    0-2: RAM bank # (?)
60    3:   Enable RAM bank in bits 0-2 (or make writable maybe?)
61    4-7: ???
62
63 Switches on the card:
64 #1 sets DMA on/off (switch pos UP = OPEN = off)
65 #2-4 sets the computer's SCSI ID number (preset at factor to 7)
66
67 Looks like bits 5-7 of register $E is device ID
68
69 From Apple II SCSI Card Tech. Ref.:
70
71 $0      R               Current SCSI data register
72 $0      W               Output data register
73 $1      R/W             Initiator command register
74 $2      R/W             Mode Select register
75 $3      R/W             Target command register
76 $4      R               SCSI bus status
77 $4      W               Select enable register
78 $5      R               Bus and Status register
79 $6      R               Input data register
80 $7      R               Reset parity/interrupts
81
82 $8      R/W             PDMA/DACK
83 $9      R               SCSI device ID
84 $A      W               Memory Bank Select register
85 $B      W               Reset 5380 IC
86 $D      W               PDMA mode enable
87 $E      R               Read DRQ status bit through D7 bit
88
89 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)
90
91 ZP locations:
92 $42             Command number
93 $43             Unit number
94 $44-45  Buffer pointer
95 $46-47  Block number
96
97 0123456789ABCDEF
98 @ABCDEFGHIJKLMNO
99 PQRSTUVWXYZ    _
100
101 So the path of execution is:
102
103  $CC00 is written to, is that the bank select writable flag (@ reg. $E)?
104  $CD00 hide bank select?
105  $CD01 restore bank select?
106  $C808 gets slot # (+$20)
107  $C809 gets 0
108  - Bank 11:0
109    $C80B gets set with $98 to signal we've been there already
110    $5D gets flags (6 = running on GS, 5 = bit 6 of reg. $C is set)
111      [could it be that bit 6 of $C is physical DMA enable switch?]
112    $5E is the slot # (+$20)
113    $C80C gets the contents of $5D
114    $C893 gets set with $80 (signal we're in I set mode)
115    $C896 is set with GS Speed Register (0 on non-GS models)
116    $C807 gets set with the SP
117    execution then jumps to...
118  - Bank 15:0
119    $C809 gets $40 (& $BF32 as well!)
120    $C80A gets 0
121    Calls bank 3:0
122    - Bank 3:0 (Look for bootable drive)
123      $C883 gets 0
124      $C815 gets 0
125      $C80D gets 0 (# of drives found?)
126      $C80F gets 0
127      $C8DA gets: Device ID from $E is massaged and changed into a single bit
128      Calls bank 21:3
129      - Bank 21:3
130        Stores $80 (RST) in reg. $1, burns some cycles, stores 0 in reg. $1
131          [Looks like ASSERT /RST]
132        burn cycles, but burn most if $C8DA == 4
133      Clears 32 bytes @ $C92F
134      $C817 gets $40 |_________________
135      $C818 gets 0   | Failure countdown
136      $C8DB gets SCSI ID # from loop (bit field)
137      $4F gets cleared (error flag)
138      Calls $CF5F (send command to device?)
139        So the buffer (@ $C923) looks like so before the call:
140 *      00 00 00 00 00 00 .. .. .. .. .. .. C3 C9 00
141        ^$60/1 points here                  ^$56/7 points here ($62 = $58 = 0)
142 *      Puts $C9C3 into $C92F/30, zeroes $C931
143        Then calls bank 16:0
144      - Bank 16:0
145        Stores to $CD00
146        Calls $CDD0
147          Clears bit 1 from reg. $2, bits 0 & 1 from $C
148            [Looks like it clears the DMA MODE bit]
149 *        Clears $4F, $C806, $C88F, $C890, $C8EE-F0
150          Sets bit 7 of reg. $F
151        Calls $CECE
152          Gets reg. $4, checks for 0, returns success if so
153            [R is SCSI Bus Status]
154          Masks bits 1-5 & 7, checks for 2 or 4, returns success if so
155            [bit 2 = /I/O, bit 1 = /SEL]
156          Else, $81 -> $C88F, returns failure (set bit 7 of $C806, sets C)
157        Calls $CF42
158          Returns since $C893 has $80 in it
159        Calls $CC24 (Arbitrate phase)
160          Zeroes reg. $3
161            [Target Command, set Data Out]
162          Toggles bit 7 of reg. $E (ON-off-ON)
163          Puts host ID(?) in reg. $0
164            [W: Output Data - sends data on SCSI bus]
165          Loop:
166          Puts 0 in reg. $2, then sets bit 0 of reg. $2
167            [bit 0 is ARBITRATE, requires SCSI device ID in $0]
168          Gets reg. $C, checks bit 4
169            If clear, then toggle reg. $E (ON-off-ON) & count down to failure
170          Check bit 6 of reg. $1, loop back if not set
171            [Initiator Command. bit 6 AIP, if set bus free detected]
172          Check bit 5 of reg. $1, loop back if not clear
173            [Initiator Command, bit 5 LA, if set, bus was lost]
174          Check reg. $0 to see if it's same as what's in $C8DA
175            [R: Current SCSI Data]
176            If not, see if it's >= to EORed value & loop back if it is
177          Checks bit 5 of reg. $1, loop back if not clear
178            [Initiator Command, bit 5 LA, if set, bus was lost]
179          Sets bits 1 & 2 of reg. $1, clear bits 5 & 6 of same
180            [Initiator Command: 1 = ASSERT /ATN, 2 = ASSERT /SEL, clear AIP, LA]
181          Clear C and return if success, set $C88F to $80 & set C if failure
182        Calls $CC7A if succesful:
183          Zeroes out reg. $4
184            [Select Enable: disable interrupts]
185          Stores $C8DA ORed with $C8DB into reg. $0
186            [W: Output Data - writing ?]
187          Set bits 0 & 6 in reg. $1, clear 5 & 6 in reg. $1
188            [W: 0- ASSERT DATA BUS, 6- TEST MODE; 5- unused(?), 6- TEST MODE off]
189          Clear bit 0 in reg. $2
190            [W: Clear ARBITRATE]
191          Puts contents of $C8DC +set bit 7 into $C821
192          Clears bit 3 in reg. $1
193            [W: 3- ASSERT /BSY (0 disconnects from bus)]
194          Calls $CD51
195            Toggle bit 7 of reg. $E (ON-off-ON)
196            Wait for bit 6 of reg. $4 to come on, if not, set C (signal failure)
197              [R: bit 6- /BSY]
198          Clears bit 2 in reg. $1
199            [W: ASSERT /SEL (0 de-asserts)]
200          Clears bits 1, 5, 6 in reg. $1
201            [W: 1- /ATN, 5- unused(?), 6- TEST MODE]
202          Clears bit 0 in reg. $1
203            [W: ASSERT DATA BUS (0 de-asserts)]
204          Signals success (C = 0) or failure (C = 1, $C88F = $81)
205        Calls $CF58
206          Returns since $C893 has $80 in it still
207        Calls $CCE4
208          Checks if bit 4 of reg. $C is set, if not, toggle bit 7 of reg. $E
209          Checks $4, if either of bits 1 & 6 are set, if not, signal failure
210          If only bit 2 or 2 & 6 is set, loop back to beginning of call
211          Clears bit 1 of reg. $2, then restores it to what it was
212            [W: 1- DMA MODE]
213          Checks for bit 5 of reg. $4, if not set, loop back to begin
214            [R: 5- /REQ]
215          Moves $C81F into $C820
216          Restores reg. $4 from Y, masks off bits 2-4 and puts it in $C81F
217            [R: 4- /MSG, 3- /C/D, 2- /I/O]
218          Puts prev. value r. shifted 1 into $C82B
219          Uses that as index into jump table
220          R. shifts again by 1 and stuffs into reg. $3
221            [W: Target Command- writes /MSG, /C/D, /I/O]
222          Calls $CD48
223            Using Y as index, push value pair @ $CFB4 onto stack & return to call
224            Calls a routine from 0-7...
225              0-1 goes to $6E6C or copies $56-8 into $C81C-E, calls bank 18:0
226              - Bank 18:0
227                ...
228                Calls bank 20:0 or 1 (0 for read, 1 for write--PIO mode)
229              2 calls bank 17:0 (/C/D)
230              3 calls bank 17:3 (/C/D + /I/O)
231              4-5 signals failure & returns (bit 4: /MSG, no /C/D = failure)
232              6 calls bank 17:2 (/MSG + /C/D)
233                [During init, it comes here...]
234                Gets $C821, compares it to 1, if so, signal failure & return
235                Calls $CE79
236                  a
237              7 calls bank 17:1 (/MSG + /C/D + /I/O)
238          If bit 7 of $C806 is clear, loop back to begin
239        Calls $CDA0
240          Does some error checking on $C88F and $C8EC
241        Jumps to $CE18
242        Clears bit 1 from reg. $2, bits 0-1 from reg. $C
243        Moves $C88F into $4F
244        If it's 0 or $8E, or reg. $4 is 0, skip over next
245          Calls $CE6C
246          Moves $C88F into $4F
247        Zeroes out regs. $1, $2, $3, $C
248        Stores to $CD01
249      [Returns to $CC6D in bank 3:0]
250      Calls $CC9F (Function 1 - INQUIRY + more
251        Zeroes $C8CF, $C892
252        Calls $CD0E
253          [12 00 00 00 1E 00 .. .. .. .. .. .. C3 C9 00 .. 1E]
254          Calls bank 16:0 (Do INQUIRY)
255          if $C9C3 (1st byte of INQUIRY data) == $10
256            $C892 <- $80
257            $C8CB <- $06
258            $C8B9 <- $F8
259            $C8CC <- $C0
260          else if == 2 or 6,
261            $C892 <- $40
262            $C8CF |= $0C
263          else (depending on 1st byte),
264            (5=CDROM, 6=DA Tape drive, 7=HD, 8=Scanner, 9=Printer, 3=nonspecific)
265            $C8CB <- 07 06 09 FF FF 05 08
266            $C8B9 <- C0 C0 A0 00 00 C0 A0
267            $C8CC <- F8 F8 78 FF FF B4 70
268          Sets bit 5, clears bit 6 in $C8CC
269          if bits 4-5, 7 are set, set bit 0 of $C8CF
270          Copies 16 bytes of returned data from $C9C3 + $17 to $C8BB
271          $C8CE <- $30
272          $C8CD <- $00
273        Calls bank 21:1 (lock CD-ROM?)
274          Does PREVENT ALLOW MEDIUM REMOVAL if $C8CB == 5
275          $C927 <- $01
276        Calls $CDDD (MODE SENSE/MODE SELECT)
277        Calls $CEA8 (READ CAPACITY)
278          Calls 16:0 with command READ CAPACITY, data returned @ $C9C3 (8 bytes)
279          $C8AB <- $C9C6
280          $C8AA <- $C9C5
281          $C8A9 <- $C9C4
282          if $C8A9 =! 0, set bit 0 of $C8CF
283          $C8A8 <- $C9C3
284          if $C8A8 != 0, set bit 0 of $C8CF
285          $C8AF <- $C9C7, save flags
286          $C8AE <- $C9C8, save flags
287          $C8AD <- $C9C9, save in Y
288          $C8AC <- $C9CA
289          Zeroes error flag ($4F)
290        $C8CF |= 0x0C
291        Calls $CFC7 (bank 4:0 direct)
292        - Bank 4:0
293          Calls $CD65 (READ--reads 512 bytes from LBA set from $C8D2 + 1)
294          Calls $CDDA, sets carry if 1st two bytes are not 'PM'
295          Calls $CD39
296          Calls $CD1A
297          $C8D0 <- $C80F
298          Calls $CDF1
299        [Returns to $CCDB in bank 3:0]
300        Loops back if bit 7 of $C892 is clear
301        Calls $CD05 (bank 21:2 direct--unlock CD-ROM?)
302        Adds 1 to $C8DC
303        Loops back if $C8DC != 8
304      ...
305    [Returns to $CC10 in bank 15:0]
306    Checks if call was successful (if not jumps to bank 11:1)
307    execution jumps to...
308  - Bank 11:2 ($CD9A)
309    Puts 1 in $43 (unit #), $44
310    Zeroes out $46-49 (block # [2], ??? [2])
311    Puts $08 in $41, zeroes out $40, $42 (command)
312    Calls bank 9:0
313    - Bank 9:0
314      ...
315      Calls bank 16:0
316
317 SCSI Phases
318 -----------
319
320 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).
321 */
322 static bool DATA_BUS = false;
323 static bool DMA_MODE = false;
324 static bool BSY = false;
325 static bool ATN = false;
326 static bool SEL = false;
327 static bool ACK = false;
328 static bool RST = false;
329 static bool MSG = false;
330 static bool C_D = false;
331 static bool I_O = false;
332 static bool REQ = false;
333 static bool DEV_BSY = false;
334 static bool DRQ = false;
335 static bool DACK = false;
336 static uint8_t devMode = 8;
337 static uint8_t cmdLength;
338 static uint8_t cmd[256];
339 static uint32_t bytesToSend;
340 static uint8_t * buf;
341 static uint32_t bufPtr;
342
343
344 static void RunDevice(void)
345 {
346 //WriteLog("   >>> RUNNING HD...\n");
347         // Let's see where it's really going...
348 /*      if (mainCPU.pc == 0xCE7E)
349                 dumpDis = true;//*/
350
351         static uint8_t readCapacity[8] = { 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00 };
352         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' };
353
354         enum {
355                 DVM_DATA_OUT = 0, DVM_DATA_IN = 1, DVM_COMMAND = 2, DVM_STATUS = 3,
356                 DVM_MESSAGE_OUT = 6, DVM_MESSAGE_IN = 7, DVM_BUS_FREE, DVM_ARBITRATE,
357                 DVM_SELECT
358         };
359
360         if (RST)
361         {
362 WriteLog("   >>> DEVICE RESET...\n");
363                 devMode = DVM_BUS_FREE;
364                 DEV_BSY = false;
365                 return;
366         }
367
368         switch (devMode)
369         {
370         case DVM_BUS_FREE:
371                 if (SEL)//(BSY && SEL)
372                         devMode = DVM_ARBITRATE;
373
374                 break;
375         case DVM_ARBITRATE:
376 //WriteLog("   >>> ARBITRATE PHASE (BSY=%i SEL=%i DATA_BUS=%i [%02X])\n", BSY, SEL, DATA_BUS, reg[0]);
377                 if (!BSY && SEL && DATA_BUS && (reg[0] & 0x40))
378                         devMode = DVM_SELECT, DEV_BSY = true;
379                 else if (!BSY && !SEL)
380                         devMode = DVM_BUS_FREE;
381
382                 break;
383         case DVM_SELECT:
384 WriteLog("   >>> SELECT PHASE\n");
385                 if (ATN)
386                 {
387                         MSG = true, C_D = true, I_O = false;
388                         devMode = DVM_MESSAGE_OUT;
389                         REQ = true;
390                 }
391                 else
392                 {
393                 // If no ATN is asserted, go to COMMAND I guess?
394                 // Let's try it
395 // errrr, no.  this does not work. Or does it???
396                         MSG = false, C_D = true, I_O = false;
397                         devMode = DVM_COMMAND;
398                         cmdLength = 0;
399                 }
400
401                 break;
402         case DVM_DATA_OUT:
403 WriteLog("   >>> DATA OUT PHASE\n");
404                 if (!ACK)
405                         REQ = true;
406
407                 if (DMA_MODE)
408                 {
409                         if (!DACK)
410                         {
411                                 // We just send zeroes for now...
412                                 reg[6] = 0;
413                                 DRQ = true;
414                         }
415                         else if (DRQ && DACK)
416                         {
417                                 DRQ = false;
418                                 DACK = false;
419                                 bytesToSend--;
420
421                                 if (bytesToSend == 0)
422                                 {
423                                         REQ = false;
424                                         MSG = false, C_D = true, I_O = true;
425                                         devMode = DVM_STATUS;
426                                 }
427                         }
428                 }
429
430                 break;
431         case DVM_DATA_IN:
432 WriteLog("   >>> DATA IN PHASE (bts=%u)\n", bytesToSend);
433                 if (!ACK)
434                         REQ = true;
435
436                 if (DMA_MODE)
437                 {
438                         if (!DACK)
439                         {
440                                 // We just send zeroes for now...
441                                 if (buf == NULL)
442                                         reg[6] = 0;
443                                 else
444                                         reg[6] = buf[bufPtr];
445
446                                 DRQ = true;
447                         }
448                         else if (DRQ && DACK)
449                         {
450                                 DRQ = false;
451                                 DACK = false;
452                                 bytesToSend--;
453                                 bufPtr++;
454
455                                 if (bytesToSend == 0)
456                                 {
457                                         REQ = false;
458                                         MSG = false, C_D = true, I_O = true;
459                                         devMode = DVM_STATUS;
460                                         buf = NULL;
461                                 }
462                         }
463                 }
464
465                 break;
466         case DVM_COMMAND:
467 WriteLog("   >>> COMMAND PHASE\n");
468                 if (!ACK)
469                         REQ = true;
470                 else if (REQ && ACK)
471                 {
472                         cmd[cmdLength++] = reg[0];
473                         WriteLog("HD: Write to target value $%02X\n", reg[0]);
474                         REQ = false;
475                 }
476
477                 // Handle "Test Unit Ready" command
478                 if ((cmd[0] == 0) && (cmdLength == 6))
479                 {
480                         WriteLog("HD: Received command TEST UNIT READY\n");
481                         REQ = false;
482                         // Drive next phase
483                         MSG = false, C_D = true, I_O = true;
484                         devMode = DVM_STATUS;
485                 }
486                 // Handle "Request Sense" command
487                 else if ((cmd[0] == 0x03) && (cmdLength == 6))
488                 {
489                         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]);
490                         REQ = false;
491                         // Drive next phase
492                         MSG = false, C_D = false, I_O = true;
493                         devMode = DVM_DATA_IN;
494                         bytesToSend = cmd[4];
495                 }
496                 // Handle "Read" (6) command
497                 else if ((cmd[0] == 0x08) && (cmdLength == 6))
498                 {
499                         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]);
500                         REQ = false;
501                         // Drive next phase
502                         MSG = false, C_D = false, I_O = true;
503                         devMode = DVM_DATA_IN;
504                         bytesToSend = cmd[4] * 512; // amount is set in blocks
505                 }
506                 // Handle "Inquire" command
507                 else if ((cmd[0] == 0x12) && (cmdLength == 6))
508                 {
509                         WriteLog("HD: Received command INQUIRE [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]);
510                         REQ = false;
511                         // Drive next phase
512                         MSG = false, C_D = false, I_O = true;
513                         devMode = DVM_DATA_IN;
514                         bytesToSend = cmd[4];
515                         buf = inquireData;
516                         bufPtr = 0;
517                 }
518                 // Handle "Mode Select" command
519                 else if ((cmd[0] == 0x15) && (cmdLength == 6))
520                 {
521                         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]);
522                         REQ = false;
523                         // Drive next phase
524                         MSG = false, C_D = false, I_O = false;
525                         devMode = DVM_DATA_OUT;
526                         bytesToSend = cmd[4];
527                 }
528                 // Handle "Mode Sense" command
529                 else if ((cmd[0] == 0x1A) && (cmdLength == 6))
530                 {
531                         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]);
532                         REQ = false;
533                         // Drive next phase
534                         MSG = false, C_D = false, I_O = true;
535                         devMode = DVM_DATA_IN;
536                         bytesToSend = cmd[4];
537                 }
538                 // Handle "Read Capacity" command
539                 else if ((cmd[0] == 0x25) && (cmdLength == 10))
540                 {
541                         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]);
542                         REQ = false;
543                         // Drive next phase
544                         MSG = false, C_D = false, I_O = true;
545                         devMode = DVM_DATA_IN;
546                         bytesToSend = 8;//cmd[4];
547                         buf = readCapacity;
548                         bufPtr = 0;
549                 }
550                 // Handle "Read" (10) command
551                 else if ((cmd[0] == 0x28) && (cmdLength == 10))
552                 {
553                         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]);
554                         REQ = false;
555                         // Drive next phase
556                         MSG = false, C_D = false, I_O = true;
557                         devMode = DVM_DATA_IN;
558                         bytesToSend = ((cmd[7] << 8) | cmd[8]) * 512; // amount is set in blocks
559                         uint32_t lba = (cmd[2] << 24) | (cmd[3] << 16) | (cmd[4] << 8) | cmd[5];
560                         buf = &hdData[(lba * 512) + 0x40];
561                         bufPtr = 0;
562                 }
563                 else if ((cmdLength == 6) && ((cmd[0] & 0xE0) == 0))
564                 {
565                         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]);
566                 }
567                 else if ((cmdLength == 10) && (((cmd[0] & 0xE0) == 0x20) || ((cmd[0] & 0xE0) == 0x40)))
568                 {
569                         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]);
570                 }
571
572                 break;
573         case DVM_STATUS:
574 WriteLog("   >>> STATUS PHASE\n");
575                 if (!ACK)
576                 {
577                         // Return A-OK for everything for now...
578                         reg[0] = 0;
579                         REQ = true;
580                 }
581                 else if (REQ && ACK)
582                 {
583                         REQ = false;
584                         // Drive next phase
585                         MSG = true, C_D = true, I_O = true;
586                         devMode = DVM_MESSAGE_IN;
587                 }
588
589                 break;
590         case DVM_MESSAGE_OUT:
591 WriteLog("   >>> MESSAGE OUT PHASE\n");
592                 if (REQ && ACK)
593                 {
594                         uint8_t msg = reg[0];
595                         WriteLog("HD: Write to target value $%02X\n", msg);
596                         REQ = false;
597                         // Drive next phase
598                         MSG = false, C_D = true, I_O = false;
599                         devMode = DVM_COMMAND;
600                         cmdLength = 0;
601                 }
602
603                 break;
604         case DVM_MESSAGE_IN:
605 WriteLog("   >>> MESSAGE IN PHASE\n");
606                 if (!ACK)
607                 {
608                         // Return A-OK for everything for now...
609                         reg[0] = 0;
610                         REQ = true;
611                 }
612                 else if (REQ && ACK)
613                 {
614                         REQ = false;
615                         // Drive next phase
616                         MSG = false, C_D = false, I_O = false;
617                         DEV_BSY = false;
618                         devMode = DVM_BUS_FREE;
619                 }
620
621                 break;
622         }
623 }
624
625
626 static uint8_t SlotIOR(uint16_t address)
627 {
628         // This should prolly go somewhere else...
629         RunDevice();
630
631         char SCSIName[16][256] = {
632                 "(RO) Current SCSI Data",
633                 "Initiator Command",
634                 "Mode",
635                 "Target Command",
636                 "(RO) Current SCSI Bus Status",
637                 "(RO) Bus and Status",
638                 "(RO) Input Data",
639                 "(RO) Reset Parity/Interrupt",
640                 "DMA Address LO",
641                 "DMA Address HI",
642                 "DMA Count LO",
643                 "DMA Count HI",
644                 "$C",
645                 "$D",
646                 "Bank/SCSI ID",
647                 "$F"
648         };
649
650         uint8_t response = reg[address & 0x0F];
651
652         switch (address & 0x0F)
653         {
654                 case 0x00:
655                         // (RO) Current SCSI Data register
656                         break;
657                 case 0x01:
658                         // Initiator Command register.  Bits, from hi to lo:
659                         // ASS. /RST, AIP, LA, ASS. /ACK, A./BSY, A./SEL, A./ATN, DATA BUS
660
661                         // Simulate ARBITRATE signal
662                         if (reg[2] & 0x01)
663                                 response |= 0x40;
664
665                         break;
666                 case 0x02:
667                         // Mode register (chip control)
668                         break;
669                 case 0x03:
670                         // Target Command register (SCSI bus info xfer phase)
671                         break;
672                 case 0x04:
673                         // (RO) Current SCSI Bus Status register:  Bits from hi to lo:
674                         // /RST, /BSY, /REQ, /MSG, /C/D, /I/O, /SEL, /DBP
675 if (((mainCPU.pc != 0xCD7C) && (mainCPU.pc != 0xCD5F)) || (bank != 16))
676         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]);
677
678                         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);
679                         break;
680                 case 0x05:
681                 {
682                         // (RO) Bus and Status register
683                         response = (ACK ? 0x01 : 0) | (ATN ? 0x02 : 0) | (DRQ ? 0x40 : 0);
684                         uint8_t tgtMode = (MSG ? 0x04 : 0) | (C_D ? 0x02 : 0) | (I_O ? 0x01 : 0);
685
686                         if ((reg[3] & 0x07) == tgtMode)
687                                 response |= 0x08;
688
689                         break;
690                 }
691                 case 0x06:
692                         // (RO) Input Data register (read from from SCSI bus)
693                         if (DRQ)
694                                 DACK = true;
695
696                         break;
697                 case 0x07:
698                         // (RO) Reset Parity/Interrupt
699                         // Resets PARITY ERR (bit 6), IRQ (bit 5), BUSY ERROR (bit 3) in
700                         // register 5 (Bus & Status)
701                         break;
702                 case 0x0C:
703                         response = 0x10 | (dmaSwitch ? 0x40 : 0);
704                         break;
705                 case 0x0E:
706                         response = bank | (deviceID << 5);
707                         break;
708         }
709
710         if (((mainCPU.pc != 0xCD7C) && (mainCPU.pc != 0xCD5F)) || (bank != 16))
711                 WriteLog("HD Slot I/O read %s ($%02X <- $%X, PC=%04X:%u)\n", SCSIName[address & 0x0F], response, address & 0x0F, mainCPU.pc, bank);
712
713         return response;
714 }
715
716
717 static void SlotIOW(uint16_t address, uint8_t byte)
718 {
719         char SCSIName[16][256] = {
720                 "(WO) Output Data",
721                 "Initiator Command",
722                 "Mode",
723                 "Target Command",
724                 "(WO) Select Enable",
725                 "(WO) Start DMA Send",
726                 "(WO) Start DMA Target Receive",
727                 "(WO) Start DMA Initiator Receive",
728                 "DMA Address LO",
729                 "DMA Address HI",
730                 "DMA Count LO",
731                 "DMA Count HI",
732                 "$C",
733                 "$D",
734                 "Bank/SCSI ID",
735                 "$F"
736         };
737
738         switch (address & 0x0F)
739         {
740                 case 0x00:
741                         // (WO) Output Data register (data sent over SCSI bus)
742                         if (DRQ)
743                                 DACK = true;
744
745                         break;
746                 case 0x01:
747                         // Initiator Command register.  Bits, from hi to lo:
748                         // ASS. /RST, AIP, LA, ASS. /ACK, A./BSY, A./SEL, A./ATN, DATA BUS
749                         DATA_BUS = (byte & 0x01 ? true : false);
750                         ATN = (byte & 0x02 ? true : false);
751                         SEL = (byte & 0x04 ? true : false);
752                         BSY = (byte & 0x08 ? true : false);
753                         ACK = (byte & 0x10 ? true : false);
754                         RST = (byte & 0x80 ? true : false);
755                         break;
756                 case 0x02:
757                         // Mode register (chip control)
758
759                         // Dma ReQuest is reset here (as well as by hitting a pin)
760 //                      if ((byte & 0x02) == 0)
761 //                              DRQ = false;
762                         DMA_MODE = (byte & 0x02 ? true : false);
763
764                         if (!DMA_MODE)
765                                 DRQ = DACK = false;
766
767                         break;
768                 case 0x03:
769                         // Target Command register (SCSI bus info xfer phase)
770                         break;
771                 case 0x04:
772                         // (WO) Select Enable register
773                         break;
774                 case 0x05:
775                         // (WO) Start DMA Send (initiates DMA send)
776                         DRQ = true;
777                         break;
778                 case 0x06:
779                         // (WO) Start DMA Target Receive (initiate DMA receive--tgt mode)
780                         DRQ = true;
781                         break;
782                 case 0x07:
783                         // (WO) Start DMA Initiator Receive (initiate DMA receive--ini mode)
784                         DRQ = true;
785                         break;
786                 case 0x08:
787                         // Lo byte of DMA address?
788                         break;
789                 case 0x09:
790                         // Hi byte of DMA address?
791                         break;
792                 case 0x0A:
793                         // 2's complement of lo byte of transfer amount?
794                         break;
795                 case 0x0B:
796                         // 2's complement of hi byte of transfer amount?
797                         break;
798                 case 0x0C:
799                         // Control/status register?
800                         break;
801                 case 0x0D:
802                         // ???
803                         break;
804                 case 0x0E:
805                         // Bottom 5 bits of $E set the ROM bank...
806                         bank = byte & 0x1F;
807 //                      WriteLog("HD: Setting bank %u\n", bank);
808                         break;
809                 case 0x0F:
810                         // ??? RAM bank?  Seems to be
811                         ramBank = byte & 0x07;
812                         break;
813         }
814
815         WriteLog("HD Slot I/O write %s ($%02X -> $%X, PC=%04X:%u)\n", SCSIName[address & 0x0F], byte, address & 0x0F, mainCPU.pc, bank);
816         reg[address & 0x0F] = byte;
817
818         if ((address & 0x0F) == 0x0E)
819         {
820                 if (mainCPU.pc == 0xC78B)
821                 {
822                         uint16_t sp = mainCPU.sp;
823                         uint16_t pc = ram[0x100 + sp + 1] | (ram[0x100 + sp + 2] << 8);
824                         WriteLog("   *** Returning to bank %u, $%04X\n", bank, pc + 1);
825                 }
826                 else if (mainCPU.pc == 0xC768)
827                 {
828                         WriteLog("   *** Calling to bank %u:%u\n", mainCPU.a, (mainCPU.y & 0xE0) >> 5);
829                 }
830
831                 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]);
832         }
833
834         // This should prolly go somewhere else...
835         RunDevice();
836 }
837
838
839 static uint8_t SlotROM(uint16_t address)
840 {
841         return hd2ROM[address];
842 }
843
844
845 static uint8_t SlotIOExtraR(uint16_t address)
846 {
847         if (address < 0x400)
848                 return staticRAM[(ramBank * 0x400) + address];
849         else
850                 return hd2ROM[(bank * 0x400) + address - 0x400];
851 }
852
853
854 static void SlotIOExtraW(uint16_t address, uint8_t byte)
855 {
856         if (address < 0x400)
857                 staticRAM[(ramBank * 0x400) + address] = byte;
858         else
859         {
860                 WriteLog("Unhandled HD 1K ROM write ($%02X) @ $C%03X...\n", byte, address + 0x800);
861
862                 if ((mainCPU.pc == 0xCDDD) && (bank == 11))
863                         dumpDis = true;
864         }
865 }
866
867
868 void InstallHardDrive(uint8_t slot)
869 {
870         SlotData hd = { SlotIOR, SlotIOW, SlotROM, 0, SlotIOExtraR, SlotIOExtraW };
871         InstallSlotHandler(slot, &hd);
872
873         uint32_t size = 0;
874         hdData = ReadFile(settings.hdPath, &size);
875
876         WriteLog("Read Hard Drive image file, %u bytes ($%X)\n", size, size);
877 }
878