5 // (C) 2019 Underground Software
7 // This is done by emulating the Apple 2 High-Speed SCSI card.
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).
16 #include "harddrive.h"
20 #include "firmware/a2hs-scsi.h"
24 #include "v65c02.h" // For dumpDis...
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 uint8_t reg[16];
34 // Stuff that will have to GTFO of here
35 static uint8_t * hdData = NULL;
38 DVM_DATA_OUT = 0, DVM_DATA_IN = 1, DVM_COMMAND = 2, DVM_STATUS = 3,
39 DVM_MESSAGE_OUT = 6, DVM_MESSAGE_IN = 7, DVM_BUS_FREE = 8,
40 DVM_ARBITRATE = 16, DVM_SELECT = 32
43 static bool DATA_BUS = false;
44 static bool DMA_MODE = false;
45 static bool BSY = false;
46 static bool ATN = false;
47 static bool SEL = false;
48 static bool ACK = false;
49 static bool RST = false;
50 static bool MSG = false;
51 static bool C_D = false;
52 static bool I_O = false;
53 static bool REQ = false;
54 static bool DEV_BSY = false;
55 static bool DRQ = false;
56 static bool DACK = false;
57 static uint8_t devMode = DVM_BUS_FREE;
58 static uint8_t cmdLength;
59 static uint8_t cmd[256];
60 static uint32_t bytesToSend;
62 static uint32_t bufPtr;
63 static uint8_t response;
66 static inline void SetNextState(uint8_t state)
69 MSG = (state & 0x04 ? true : false);
70 C_D = (state & 0x02 ? true : false);
71 I_O = (state & 0x01 ? true : false);
76 static void RunDevice(void)
78 // Let's see where it's really going...
79 /* if (mainCPU.pc == 0xCE7E)
82 // These are SCSI messages sent in response to certain commands
83 static uint8_t readCapacity[8] = { 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00 };
84 static uint8_t inquireData[30] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'S', 'E', 'A', 'G', 'A', 'T', 'E', ' ', '3', '1', '3', '3', '7', ' ' };
85 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 };
89 devMode = DVM_BUS_FREE;
97 // We never initiate, so this we don't worry about whether or not the
100 // Likewise, we don't arbitrate either.
104 // If we're in Selection phase, see if our ID is on the bus, and, if so,
105 // go on to the next phase (since the Target drives the phase dance).
106 if ((reg[0] & 0x40) && DATA_BUS)
110 // Preset response code to "Good"
114 SetNextState(DVM_MESSAGE_OUT);
116 // If no ATN is asserted, go to COMMAND? Dunno, the firmware
117 // doesn't ever go there; it *always* starts with MESSAGE OUT.
118 SetNextState(DVM_COMMAND);
124 //WriteLog(" >>> DATA OUT PHASE (bts=%u)\n", bytesToSend);
134 else if (DRQ && DACK)
137 buf[bufPtr] = reg[0];
143 if (bytesToSend == 0)
146 SetNextState(DVM_STATUS);
155 //WriteLog(" >>> DATA IN PHASE (bts=%u)\n", bytesToSend);
163 // If there's no buffer set up, send zeroes...
164 reg[6] = (buf == NULL ? 0 : buf[bufPtr]);
167 else if (DRQ && DACK)
173 if (bytesToSend == 0)
176 SetNextState(DVM_STATUS);
190 cmd[cmdLength++] = reg[0];
194 uint8_t cmdType = (cmd[0] & 0xE0) >> 5;
196 if ((cmdType == 0) && (cmdLength == 6))
198 // Handle "Test Unit Ready" command
201 WriteLog("HD: Received command TEST UNIT READY\n");
202 SetNextState(DVM_STATUS);
204 // Handle "Request Sense" command
205 else if (cmd[0] == 0x03)
207 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]);
208 SetNextState(DVM_DATA_IN);
209 bytesToSend = cmd[4];
211 // Return error for LUNs other than 0
212 if ((cmd[1] & 0xE0) != 0)
218 // Handle "Read" (6) command
219 else if (cmd[0] == 0x08)
221 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]);
222 SetNextState(DVM_DATA_IN);
223 bytesToSend = cmd[4] * 512; // amount is set in blocks
224 uint32_t lba = ((cmd[1] & 0x1F) << 16) | (cmd[2] << 8) | cmd[3];
225 buf = (hdData != NULL ? &hdData[(lba * 512) + 0x40] : NULL);
228 // Handle "Inquire" command
229 else if (cmd[0] == 0x12)
231 WriteLog("HD: Received command INQUIRE [%02X %02X %02X %02X %02X %02X]\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5]);
232 SetNextState(DVM_DATA_IN);
233 bytesToSend = cmd[4];
237 // Reject all but LUN 0
238 if ((cmd[1] & 0xE0) != 0)
239 response = 0x02; // "Check Condition" code
241 // Handle "Mode Select" command
242 else if (cmd[0] == 0x15)
244 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]);
245 SetNextState(DVM_DATA_OUT);
246 bytesToSend = cmd[4];
248 // Handle "Mode Sense" command
249 else if (cmd[0] == 0x1A)
251 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]);
252 SetNextState(DVM_DATA_IN);
253 bytesToSend = cmd[4];
257 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]);
259 SetNextState(DVM_STATUS);
260 response = 0x02; // Check condition code
263 else if (((cmdType == 1) || (cmdType == 2)) && (cmdLength == 10))
265 // Handle "Read Capacity" command
268 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]);
269 SetNextState(DVM_DATA_IN);
270 bytesToSend = 8;//it's always 8...//cmd[4];
271 // N.B.: We need to hook this up to the actual emulated HD size...
275 // Handle "Read" (10) command
276 else if (cmd[0] == 0x28)
278 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]);
280 SetNextState(DVM_DATA_IN);
281 bytesToSend = ((cmd[7] << 8) | cmd[8]) * 512; // amount is set in blocks
282 uint32_t lba = (cmd[2] << 24) | (cmd[3] << 16) | (cmd[4] << 8) | cmd[5];
283 buf = (hdData != NULL ? &hdData[(lba * 512) + 0x40] : NULL);
286 // Handle "Write" (10) command
287 else if (cmd[0] == 0x2A)
289 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]);
291 SetNextState(DVM_DATA_OUT);
292 bytesToSend = ((cmd[7] << 8) | cmd[8]) * 512; // amount is set in blocks
293 uint32_t lba = (cmd[2] << 24) | (cmd[3] << 16) | (cmd[4] << 8) | cmd[5];
294 buf = (hdData != NULL ? &hdData[(lba * 512) + 0x40] : NULL);
299 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]);
301 SetNextState(DVM_STATUS);
302 response = 0x02; // "Check Condition" code
305 else if ((cmdType == 5) && (cmdLength == 12))
307 WriteLog("HD: Received unhandled 12 command [%02X %02X %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], cmd[10], cmd[11]);
309 SetNextState(DVM_STATUS);
310 response = 0x02; // "Check Condition" code
319 // Return A-OK for everything for now...
320 reg[0] = 0; // N.B.: This is necessary for some reason...
326 SetNextState(DVM_MESSAGE_IN);
331 case DVM_MESSAGE_OUT:
336 // WriteLog("HD: Write to target value $%02X\n", reg[0]);
338 SetNextState(DVM_COMMAND);
346 // Return appropriate response
354 SetNextState(DVM_BUS_FREE);
362 static uint8_t SlotIOR(uint16_t address)
364 // This should prolly go somewhere else...
367 uint8_t response = reg[address & 0x0F];
369 switch (address & 0x0F)
372 // (RO) Current SCSI Data register
375 // Initiator Command register. Bits, from hi to lo:
376 // ASS. /RST, AIP, LA, ASS. /ACK, A./BSY, A./SEL, A./ATN, DATA BUS
378 // Simulate ARBITRATE signal
384 // Mode register (chip control)
387 // Target Command register (SCSI bus info xfer phase)
390 // (RO) Current SCSI Bus Status register: Bits from hi to lo:
391 // /RST, /BSY, /REQ, /MSG, /C/D, /I/O, /SEL, /DBP
392 /*if (((mainCPU.pc != 0xCD7C) && (mainCPU.pc != 0xCD5F)) || (romBank != 16))
393 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]);//*/
395 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);
399 // (RO) Bus and Status register
400 response = (ACK ? 0x01 : 0) | (ATN ? 0x02 : 0) | (DRQ ? 0x40 : 0);
401 uint8_t tgtMode = (MSG ? 0x04 : 0) | (C_D ? 0x02 : 0) | (I_O ? 0x01 : 0);
403 if ((reg[3] & 0x07) == tgtMode)
409 // (RO) Input Data register (read from from SCSI bus)
415 // (RO) Reset Parity/Interrupt
416 // Resets PARITY ERR (bit 6), IRQ (bit 5), BUSY ERROR (bit 3) in
417 // register 5 (Bus & Status)
420 response = 0x10 | (dmaSwitch ? 0x40 : 0);
423 response = romBank | (deviceID << 5);
428 char SCSIName[16][256] = {
429 "(RO) Current SCSI Data",
433 "(RO) Current SCSI Bus Status",
434 "(RO) Bus and Status",
436 "(RO) Reset Parity/Interrupt",
447 if (((mainCPU.pc != 0xCD7C) && (mainCPU.pc != 0xCD5F)) || (romBank != 16))
448 WriteLog("HD Slot I/O read %s ($%02X <- $%X, PC=%04X:%u)\n", SCSIName[address & 0x0F], response, address & 0x0F, mainCPU.pc, romBank);
455 static void SlotIOW(uint16_t address, uint8_t byte)
457 switch (address & 0x0F)
460 // (WO) Output Data register (data sent over SCSI bus)
466 // Initiator Command register. Bits, from hi to lo:
467 // ASS. /RST, AIP, LA, ASS. /ACK, A./BSY, A./SEL, A./ATN, DATA BUS
468 DATA_BUS = (byte & 0x01 ? true : false);
469 ATN = (byte & 0x02 ? true : false);
470 SEL = (byte & 0x04 ? true : false);
471 BSY = (byte & 0x08 ? true : false);
472 ACK = (byte & 0x10 ? true : false);
473 RST = (byte & 0x80 ? true : false);
475 if (!(SEL || BSY || DEV_BSY))
476 devMode = DVM_BUS_FREE;
478 if (SEL && (devMode == DVM_ARBITRATE))
479 devMode = DVM_SELECT;
483 // Mode register (chip control)
484 if ((byte & 0x01) && (devMode == DVM_BUS_FREE))
485 devMode = DVM_ARBITRATE;
487 // Dma ReQuest is reset here (as well as by hitting a pin)
488 DMA_MODE = (byte & 0x02 ? true : false);
495 // Target Command register (SCSI bus info xfer phase)
498 // (WO) Select Enable register
501 // (WO) Start DMA Send (initiates DMA send)
505 // (WO) Start DMA Target Receive (initiate DMA receive--tgt mode)
509 // (WO) Start DMA Initiator Receive (initiate DMA receive--ini mode)
513 // Lo byte of DMA address?
516 // Hi byte of DMA address?
519 // 2's complement of lo byte of transfer amount?
522 // 2's complement of hi byte of transfer amount?
525 // Control/status register?
531 // Bottom 5 bits of $E set the ROM bank
532 romBank = byte & 0x1F;
535 // Bottom 3 bits of $F set the RAM bank
536 ramBank = byte & 0x07;
540 reg[address & 0x0F] = byte;
543 char SCSIName[16][256] = {
548 "(WO) Select Enable",
549 "(WO) Start DMA Send",
550 "(WO) Start DMA Target Receive",
551 "(WO) Start DMA Initiator Receive",
561 char SCSIPhase[11][256] = { "DATA OUT", "DATA IN", "COMMAND", "STATUS", "ERR4", "ERR5", "MESSAGE OUT", "MESSAGE IN", "BUS FREE", "ARBITRATE", "SELECT" };
564 WriteLog("HD Slot I/O write %s ($%02X -> $%X, PC=%04X:%u) [%s]\n", SCSIName[address & 0x0F], byte, address & 0x0F, mainCPU.pc, romBank, SCSIPhase[devMode]);
566 if ((address & 0x0F) == 0x0E)
568 if (mainCPU.pc == 0xC78B)
570 uint16_t sp = mainCPU.sp;
571 uint16_t pc = ram[0x100 + sp + 1] | (ram[0x100 + sp + 2] << 8);
572 WriteLog(" *** Returning to bank %u, $%04X\n", romBank, pc + 1);
574 else if (mainCPU.pc == 0xC768)
576 WriteLog(" *** Calling to bank %u:%u\n", mainCPU.a, (mainCPU.y & 0xE0) >> 5);
579 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]);
583 // This should prolly go somewhere else...
588 static uint8_t SlotROM(uint16_t address)
590 return a2hsScsiROM[address];
594 static uint8_t SlotIOExtraR(uint16_t address)
597 return staticRAM[(ramBank * 0x400) + address];
599 return a2hsScsiROM[(romBank * 0x400) + address - 0x400];
603 static void SlotIOExtraW(uint16_t address, uint8_t byte)
606 staticRAM[(ramBank * 0x400) + address] = byte;
609 WriteLog("HD: Unhandled HD 1K ROM write ($%02X) @ $C%03X...\n", byte, address + 0x800);
611 /* if ((mainCPU.pc == 0xCDDD) && (romBank == 11))
617 void InstallHardDrive(uint8_t slot)
619 SlotData hd = { SlotIOR, SlotIOW, SlotROM, 0, SlotIOExtraR, SlotIOExtraW };
620 InstallSlotHandler(slot, &hd);
622 // If this fails to read the file, the pointer is set to NULL
624 hdData = ReadFile(settings.hdPath, &size);
627 WriteLog("HD: Read Hard Drive image file, %u bytes ($%X)\n", size - 0x40, size - 0x40);
629 WriteLog("HD: Could not read Hard Drive image file!\n");