]> Shamusworld >> Repos - apple2/blob - src/harddrive.cpp
731f4173897e4895cc6c83967a0132256b014d11
[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 (8K static RAM, bank switched).
14 //
15
16 #include "harddrive.h"
17 #include "apple2.h"
18 #include "dis65c02.h"
19 #include "fileio.h"
20 #include "firmware/a2hs-scsi.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 romBank = 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 factory 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 static uint8_t response;
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         static uint8_t badSense[20] = { 0x70, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
354
355         enum {
356                 DVM_DATA_OUT = 0, DVM_DATA_IN = 1, DVM_COMMAND = 2, DVM_STATUS = 3,
357                 DVM_MESSAGE_OUT = 6, DVM_MESSAGE_IN = 7, DVM_BUS_FREE, DVM_ARBITRATE,
358                 DVM_SELECT
359         };
360
361         if (RST)
362         {
363 //WriteLog("   >>> DEVICE RESET...\n");
364                 devMode = DVM_BUS_FREE;
365                 DEV_BSY = false;
366                 return;
367         }
368
369         switch (devMode)
370         {
371         case DVM_BUS_FREE:
372                 if (SEL)//(BSY && SEL)
373                         devMode = DVM_ARBITRATE;
374
375                 break;
376         case DVM_ARBITRATE:
377 ////WriteLog("   >>> ARBITRATE PHASE (BSY=%i SEL=%i DATA_BUS=%i [%02X])\n", BSY, SEL, DATA_BUS, reg[0]);
378                 if (!BSY && SEL && DATA_BUS && (reg[0] & 0x40))
379                         devMode = DVM_SELECT, DEV_BSY = true;
380                 else if (!BSY && !SEL)
381                         devMode = DVM_BUS_FREE;
382
383                 break;
384         case DVM_SELECT:
385 //WriteLog("   >>> SELECT PHASE\n");
386                 // Preset response code to "Good"
387                 response = 0x00;
388
389                 if (ATN)
390                 {
391                         MSG = true, C_D = true, I_O = false;
392                         devMode = DVM_MESSAGE_OUT;
393                         REQ = true;
394                 }
395                 else
396                 {
397                 // If no ATN is asserted, go to COMMAND I guess?
398                 // Let's try it
399 // errrr, no.  this does not work. Or does it???
400                         MSG = false, C_D = true, I_O = false;
401                         devMode = DVM_COMMAND;
402                         cmdLength = 0;
403                 }
404
405                 break;
406         case DVM_DATA_OUT:
407 //WriteLog("   >>> DATA OUT PHASE (bts=%u)\n", bytesToSend);
408                 if (!ACK)
409                         REQ = true;
410
411                 if (DMA_MODE)
412                 {
413                         if (!DACK)
414                         {
415                                 DRQ = true;
416                         }
417                         else if (DRQ && DACK)
418                         {
419                                 if (buf)
420                                         buf[bufPtr] = reg[0];
421
422                                 DRQ = false;
423                                 DACK = false;
424                                 bytesToSend--;
425                                 bufPtr++;
426
427                                 if (bytesToSend == 0)
428                                 {
429                                         REQ = false;
430                                         MSG = false, C_D = true, I_O = true;
431                                         devMode = DVM_STATUS;
432                                         buf = NULL;
433                                 }
434                         }
435                 }
436
437                 break;
438         case DVM_DATA_IN:
439 //WriteLog("   >>> DATA IN PHASE (bts=%u)\n", bytesToSend);
440                 if (!ACK)
441                         REQ = true;
442
443                 if (DMA_MODE)
444                 {
445                         if (!DACK)
446                         {
447                                 // We just send zeroes for now...
448                                 if (buf == NULL)
449                                         reg[6] = 0;
450                                 else
451                                         reg[6] = buf[bufPtr];
452
453                                 DRQ = true;
454                         }
455                         else if (DRQ && DACK)
456                         {
457                                 DRQ = false;
458                                 DACK = false;
459                                 bytesToSend--;
460                                 bufPtr++;
461
462                                 if (bytesToSend == 0)
463                                 {
464                                         REQ = false;
465                                         MSG = false, C_D = true, I_O = true;
466                                         devMode = DVM_STATUS;
467                                         buf = NULL;
468                                 }
469                         }
470                 }
471
472                 break;
473         case DVM_COMMAND:
474 //WriteLog("   >>> COMMAND PHASE\n");
475                 if (!ACK)
476                         REQ = true;
477                 else if (REQ && ACK)
478                 {
479                         cmd[cmdLength++] = reg[0];
480 //                      WriteLog("HD: Write to target value $%02X\n", reg[0]);
481                         REQ = false;
482                 }
483
484                 // Handle "Test Unit Ready" command
485                 if ((cmd[0] == 0) && (cmdLength == 6))
486                 {
487                         WriteLog("HD: Received command TEST UNIT READY\n");
488                         REQ = false;
489                         // Drive next phase
490                         MSG = false, C_D = true, I_O = true;
491                         devMode = DVM_STATUS;
492                 }
493                 // Handle "Request Sense" command
494                 else if ((cmd[0] == 0x03) && (cmdLength == 6))
495                 {
496                         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]);
497                         REQ = false;
498                         // Drive next phase
499                         MSG = false, C_D = false, I_O = true;
500                         devMode = DVM_DATA_IN;
501                         bytesToSend = cmd[4];
502
503                         // Return error for LUNs other than 0
504                         if ((cmd[1] & 0xE0) != 0)
505                         {
506                                 buf = badSense;
507                                 bufPtr = 0;
508                         }
509                 }
510                 // Handle "Read" (6) command
511                 else if ((cmd[0] == 0x08) && (cmdLength == 6))
512                 {
513                         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]);
514                         REQ = false;
515                         // Drive next phase
516                         MSG = false, C_D = false, I_O = true;
517                         devMode = DVM_DATA_IN;
518                         bytesToSend = cmd[4] * 512; // amount is set in blocks
519                 }
520                 // Handle "Inquire" command
521                 else if ((cmd[0] == 0x12) && (cmdLength == 6))
522                 {
523                         WriteLog("HD: Received command INQUIRE [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]);
524                         REQ = false;
525                         // Drive next phase
526                         MSG = false, C_D = false, I_O = true;
527                         devMode = DVM_DATA_IN;
528                         bytesToSend = cmd[4];
529                         buf = inquireData;
530                         bufPtr = 0;
531
532                         // Reject all but LUN 0
533                         if ((cmd[1] & 0xE0) != 0)
534                         {
535                                 response = 0x02; // Check condition code
536 //                              MSG = false, C_D = false, I_O = false;
537 //                              DEV_BSY = false;
538 //                              devMode = DVM_BUS_FREE;
539                         }
540                 }
541                 // Handle "Mode Select" command
542                 else if ((cmd[0] == 0x15) && (cmdLength == 6))
543                 {
544                         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]);
545                         REQ = false;
546                         // Drive next phase
547                         MSG = false, C_D = false, I_O = false;
548                         devMode = DVM_DATA_OUT;
549                         bytesToSend = cmd[4];
550                 }
551                 // Handle "Mode Sense" command
552                 else if ((cmd[0] == 0x1A) && (cmdLength == 6))
553                 {
554                         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]);
555                         REQ = false;
556                         // Drive next phase
557                         MSG = false, C_D = false, I_O = true;
558                         devMode = DVM_DATA_IN;
559                         bytesToSend = cmd[4];
560                 }
561                 // Handle "Read Capacity" command
562                 else if ((cmd[0] == 0x25) && (cmdLength == 10))
563                 {
564                         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]);
565                         REQ = false;
566                         // Drive next phase
567                         MSG = false, C_D = false, I_O = true;
568                         devMode = DVM_DATA_IN;
569                         bytesToSend = 8;//cmd[4];
570                         buf = readCapacity;
571                         bufPtr = 0;
572                 }
573                 // Handle "Read" (10) command
574                 else if ((cmd[0] == 0x28) && (cmdLength == 10))
575                 {
576                         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]);
577                         REQ = false;
578                         // Drive next phase
579                         MSG = false, C_D = false, I_O = true;
580                         devMode = DVM_DATA_IN;
581                         bytesToSend = ((cmd[7] << 8) | cmd[8]) * 512; // amount is set in blocks
582                         uint32_t lba = (cmd[2] << 24) | (cmd[3] << 16) | (cmd[4] << 8) | cmd[5];
583                         buf = (hdData != NULL ? &hdData[(lba * 512) + 0x40] : NULL);
584                         bufPtr = 0;
585                 }
586                 // Handle "Write" (10) command
587                 else if ((cmd[0] == 0x2A) && (cmdLength == 10))
588                 {
589                         WriteLog("HD: Received command WRITE(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]);
590                         REQ = false;
591                         // Drive next phase
592                         MSG = false, C_D = false, I_O = false;
593                         devMode = DVM_DATA_OUT;
594                         bytesToSend = ((cmd[7] << 8) | cmd[8]) * 512; // amount is set in blocks
595                         uint32_t lba = (cmd[2] << 24) | (cmd[3] << 16) | (cmd[4] << 8) | cmd[5];
596                         buf = (hdData != NULL ? &hdData[(lba * 512) + 0x40] : NULL);
597                         bufPtr = 0;
598                 }
599                 else if ((cmdLength == 6) && ((cmd[0] & 0xE0) == 0))
600                 {
601                         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]);
602                 }
603                 else if ((cmdLength == 10) && (((cmd[0] & 0xE0) == 0x20) || ((cmd[0] & 0xE0) == 0x40)))
604                 {
605                         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]);
606                 }
607
608                 break;
609         case DVM_STATUS:
610 //WriteLog("   >>> STATUS PHASE\n");
611                 if (!ACK)
612                 {
613                         // Return A-OK for everything for now...
614                         reg[0] = 0;
615                         REQ = true;
616                 }
617                 else if (REQ && ACK)
618                 {
619                         REQ = false;
620                         // Drive next phase
621                         MSG = true, C_D = true, I_O = true;
622                         devMode = DVM_MESSAGE_IN;
623                 }
624
625                 break;
626         case DVM_MESSAGE_OUT:
627 //WriteLog("   >>> MESSAGE OUT PHASE\n");
628                 if (REQ && ACK)
629                 {
630                         uint8_t msg = reg[0];
631 //                      WriteLog("HD: Write to target value $%02X\n", msg);
632                         REQ = false;
633                         // Drive next phase
634                         MSG = false, C_D = true, I_O = false;
635                         devMode = DVM_COMMAND;
636                         cmdLength = 0;
637                 }
638
639                 break;
640         case DVM_MESSAGE_IN:
641 //WriteLog("   >>> MESSAGE IN PHASE\n");
642                 if (!ACK)
643                 {
644                         // Return A-OK for everything for now...
645                         reg[0] = response;
646                         REQ = true;
647                 }
648                 else if (REQ && ACK)
649                 {
650                         REQ = false;
651                         // Drive next phase
652                         MSG = false, C_D = false, I_O = false;
653                         DEV_BSY = false;
654                         devMode = DVM_BUS_FREE;
655                 }
656
657                 break;
658         }
659 }
660
661
662 static uint8_t SlotIOR(uint16_t address)
663 {
664         // This should prolly go somewhere else...
665         RunDevice();
666
667         char SCSIName[16][256] = {
668                 "(RO) Current SCSI Data",
669                 "Initiator Command",
670                 "Mode",
671                 "Target Command",
672                 "(RO) Current SCSI Bus Status",
673                 "(RO) Bus and Status",
674                 "(RO) Input Data",
675                 "(RO) Reset Parity/Interrupt",
676                 "DMA Address LO",
677                 "DMA Address HI",
678                 "DMA Count LO",
679                 "DMA Count HI",
680                 "$C",
681                 "$D",
682                 "Bank/SCSI ID",
683                 "$F"
684         };
685
686         uint8_t response = reg[address & 0x0F];
687
688         switch (address & 0x0F)
689         {
690                 case 0x00:
691                         // (RO) Current SCSI Data register
692                         break;
693                 case 0x01:
694                         // Initiator Command register.  Bits, from hi to lo:
695                         // ASS. /RST, AIP, LA, ASS. /ACK, A./BSY, A./SEL, A./ATN, DATA BUS
696
697                         // Simulate ARBITRATE signal
698                         if (reg[2] & 0x01)
699                                 response |= 0x40;
700
701                         break;
702                 case 0x02:
703                         // Mode register (chip control)
704                         break;
705                 case 0x03:
706                         // Target Command register (SCSI bus info xfer phase)
707                         break;
708                 case 0x04:
709                         // (RO) Current SCSI Bus Status register:  Bits from hi to lo:
710                         // /RST, /BSY, /REQ, /MSG, /C/D, /I/O, /SEL, /DBP
711 /*if (((mainCPU.pc != 0xCD7C) && (mainCPU.pc != 0xCD5F)) || (romBank != 16))
712         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]);//*/
713
714                         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);
715                         break;
716                 case 0x05:
717                 {
718                         // (RO) Bus and Status register
719                         response = (ACK ? 0x01 : 0) | (ATN ? 0x02 : 0) | (DRQ ? 0x40 : 0);
720                         uint8_t tgtMode = (MSG ? 0x04 : 0) | (C_D ? 0x02 : 0) | (I_O ? 0x01 : 0);
721
722                         if ((reg[3] & 0x07) == tgtMode)
723                                 response |= 0x08;
724
725                         break;
726                 }
727                 case 0x06:
728                         // (RO) Input Data register (read from from SCSI bus)
729                         if (DRQ)
730                                 DACK = true;
731
732                         break;
733                 case 0x07:
734                         // (RO) Reset Parity/Interrupt
735                         // Resets PARITY ERR (bit 6), IRQ (bit 5), BUSY ERROR (bit 3) in
736                         // register 5 (Bus & Status)
737                         break;
738                 case 0x0C:
739                         response = 0x10 | (dmaSwitch ? 0x40 : 0);
740                         break;
741                 case 0x0E:
742                         response = romBank | (deviceID << 5);
743                         break;
744         }
745
746 /*      if (((mainCPU.pc != 0xCD7C) && (mainCPU.pc != 0xCD5F)) || (romBank != 16))
747                 WriteLog("HD Slot I/O read %s ($%02X <- $%X, PC=%04X:%u)\n", SCSIName[address & 0x0F], response, address & 0x0F, mainCPU.pc, romBank);//*/
748
749         return response;
750 }
751
752
753 static void SlotIOW(uint16_t address, uint8_t byte)
754 {
755         char SCSIName[16][256] = {
756                 "(WO) Output Data",
757                 "Initiator Command",
758                 "Mode",
759                 "Target Command",
760                 "(WO) Select Enable",
761                 "(WO) Start DMA Send",
762                 "(WO) Start DMA Target Receive",
763                 "(WO) Start DMA Initiator Receive",
764                 "DMA Address LO",
765                 "DMA Address HI",
766                 "DMA Count LO",
767                 "DMA Count HI",
768                 "$C",
769                 "$D",
770                 "Bank/SCSI ID",
771                 "$F"
772         };
773
774         switch (address & 0x0F)
775         {
776                 case 0x00:
777                         // (WO) Output Data register (data sent over SCSI bus)
778                         if (DRQ)
779                                 DACK = true;
780
781                         break;
782                 case 0x01:
783                         // Initiator Command register.  Bits, from hi to lo:
784                         // ASS. /RST, AIP, LA, ASS. /ACK, A./BSY, A./SEL, A./ATN, DATA BUS
785                         DATA_BUS = (byte & 0x01 ? true : false);
786                         ATN = (byte & 0x02 ? true : false);
787                         SEL = (byte & 0x04 ? true : false);
788                         BSY = (byte & 0x08 ? true : false);
789                         ACK = (byte & 0x10 ? true : false);
790                         RST = (byte & 0x80 ? true : false);
791                         break;
792                 case 0x02:
793                         // Mode register (chip control)
794
795                         // Dma ReQuest is reset here (as well as by hitting a pin)
796                         DMA_MODE = (byte & 0x02 ? true : false);
797
798                         if (!DMA_MODE)
799                                 DRQ = DACK = false;
800
801                         break;
802                 case 0x03:
803                         // Target Command register (SCSI bus info xfer phase)
804                         break;
805                 case 0x04:
806                         // (WO) Select Enable register
807                         break;
808                 case 0x05:
809                         // (WO) Start DMA Send (initiates DMA send)
810                         DRQ = true;
811                         break;
812                 case 0x06:
813                         // (WO) Start DMA Target Receive (initiate DMA receive--tgt mode)
814                         DRQ = true;
815                         break;
816                 case 0x07:
817                         // (WO) Start DMA Initiator Receive (initiate DMA receive--ini mode)
818                         DRQ = true;
819                         break;
820                 case 0x08:
821                         // Lo byte of DMA address?
822                         break;
823                 case 0x09:
824                         // Hi byte of DMA address?
825                         break;
826                 case 0x0A:
827                         // 2's complement of lo byte of transfer amount?
828                         break;
829                 case 0x0B:
830                         // 2's complement of hi byte of transfer amount?
831                         break;
832                 case 0x0C:
833                         // Control/status register?
834                         break;
835                 case 0x0D:
836                         // ???
837                         break;
838                 case 0x0E:
839                         // Bottom 5 bits of $E set the ROM bank
840                         romBank = byte & 0x1F;
841                         break;
842                 case 0x0F:
843                         // Bottom 3 bits of $F set the RAM bank
844                         ramBank = byte & 0x07;
845                         break;
846         }
847
848 /*      WriteLog("HD Slot I/O write %s ($%02X -> $%X, PC=%04X:%u)\n", SCSIName[address & 0x0F], byte, address & 0x0F, mainCPU.pc, romBank);//*/
849         reg[address & 0x0F] = byte;
850
851 /*      if ((address & 0x0F) == 0x0E)
852         {
853                 if (mainCPU.pc == 0xC78B)
854                 {
855                         uint16_t sp = mainCPU.sp;
856                         uint16_t pc = ram[0x100 + sp + 1] | (ram[0x100 + sp + 2] << 8);
857                         WriteLog("   *** Returning to bank %u, $%04X\n", romBank, pc + 1);
858                 }
859                 else if (mainCPU.pc == 0xC768)
860                 {
861                         WriteLog("   *** Calling to bank %u:%u\n", mainCPU.a, (mainCPU.y & 0xE0) >> 5);
862                 }
863
864                 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]);
865         }//*/
866
867         // This should prolly go somewhere else...
868         RunDevice();
869 }
870
871
872 static uint8_t SlotROM(uint16_t address)
873 {
874         return a2hsScsiROM[address];
875 }
876
877
878 static uint8_t SlotIOExtraR(uint16_t address)
879 {
880         if (address < 0x400)
881                 return staticRAM[(ramBank * 0x400) + address];
882         else
883                 return a2hsScsiROM[(romBank * 0x400) + address - 0x400];
884 }
885
886
887 static void SlotIOExtraW(uint16_t address, uint8_t byte)
888 {
889         if (address < 0x400)
890                 staticRAM[(ramBank * 0x400) + address] = byte;
891         else
892 //      {
893                 WriteLog("HD: Unhandled HD 1K ROM write ($%02X) @ $C%03X...\n", byte, address + 0x800);
894
895 /*              if ((mainCPU.pc == 0xCDDD) && (romBank == 11))
896                         dumpDis = true;//*/
897 //      }
898 }
899
900
901 void InstallHardDrive(uint8_t slot)
902 {
903         SlotData hd = { SlotIOR, SlotIOW, SlotROM, 0, SlotIOExtraR, SlotIOExtraW };
904         InstallSlotHandler(slot, &hd);
905
906         // If this fails to read the file, the pointer is set to NULL
907         uint32_t size = 0;
908         hdData = ReadFile(settings.hdPath, &size);
909
910         if (hdData)
911                 WriteLog("HD: Read Hard Drive image file, %u bytes ($%X)\n", size - 0x40, size - 0x40);
912         else
913                 WriteLog("HD: Could not read Hard Drive image file!\n");
914 }
915