diff --git a/firmware/HDD/HDDRVR.BIN b/firmware/HDD/HDDRVR.BIN index e5c8b10c..6dba7143 100644 Binary files a/firmware/HDD/HDDRVR.BIN and b/firmware/HDD/HDDRVR.BIN differ diff --git a/firmware/HDD/hddrvr.a65 b/firmware/HDD/hddrvr.a65 index 3cc089f7..7d69c053 100644 --- a/firmware/HDD/hddrvr.a65 +++ b/firmware/HDD/hddrvr.a65 @@ -26,7 +26,7 @@ ; ; Modified by Tom Charlesworth: -; . Updated so it can be assembled by ACME 0.96.4 +; . Updated so it can be assembled by ACME 0.97 ; . Fixed so that ProDOS entrypoint is $c70a (26 Dev 2007) (Bug #12723) ; . Modified to support Apple Oasis' entrypoint: $c761 (8 Sept 2012) (Feature #5557) ; . Added support for SmartPort entrypoint (20 Oct 2012) @@ -34,10 +34,11 @@ ; . GH#370 (Robert Hoem, 27 Oct 2016): ; . Added a check against open-apple during boot to route boot to slot 6 ; . This happens after the first two blocks are loaded from the HD. -; . GH#319: smartport return address wrong when crossing page +; . GH#319: SmartPort return address wrong when crossing page +; . GH#996: Make code slot-independent (so HDD controller card can go into any slot) +; . Moved the 512-byte block read into AppleWin's HDD emulation (to mirror the block write command) ; TODO: -; . Make code relocatable (so HDD controller card can go into any slot) -; . Remove support for Entrypoint_C746 (old AppleWin) & Entrypoint_C761 (Apple Oasis) +; . Remove support for Entrypoint_Cs46 (old AppleWin) & Entrypoint_Cs61 (Apple Oasis) ; - provide a utility to convert these to use Entrypoint_ProDOS ; . Check SmartPort: Is it OK to trash Y and $42,..,$47 ? ; @@ -47,31 +48,32 @@ !sl "hddrvr.labels" ; constants -hd_execute = $c0f0 -hd_error = $c0f1 -hd_command = $c0f2 -hd_unitnum = $c0f3 -hd_memblock = $c0f4 -hd_diskblock = $c0f6 -hd_nextbyte = $c0f8 +hd_execute = $c080 +hd_error = $c081 ; b7=busy, b0=error +hd_command = $c082 +hd_unitnum = $c083 +hd_memblock = $c084 +hd_diskblock = $c086 +;hd_nextbyte = $c088 ; legacy read-only port (still supported by AppleWin) command = $42 unitnum = $43 memblock = $44 diskblock = $46 -slot6 = $c600 +slot6 = $C600 OS = $0801 BUTTON0 = $C061 -; The Autoboot rom will call this. -; This is also the entry point for such things as IN#7 and PR#7 - -;; code -*= $c700 ; org $c700 +;====================================== !zone code +*= $0000 ; org $0000 - position-independent code, so doesn't matter (but the other fixed org positions need to be on the same page) + +; The Autoboot rom will call this. +; This is also the entry point for such things as IN#7 and PR#7 + start ; Autoboot and ProDOS look at the following few opcodes to detect block devices @@ -83,44 +85,23 @@ start lda #$3C bne Bootstrap -Entrypoint_ProDOS ; $c70a - ProDOS entrypoint +Entrypoint_ProDOS ; $Cn0A - ProDOS entrypoint sec bcs Entrypoint -Entrypoint_SmartPort ; $c70d - SmartPort entrypoint +Entrypoint_SmartPort ; $Cn0D - SmartPort entrypoint clc - -Entrypoint ; $c70e - entrypoint? - bcs cmdproc - bcc SmartPort -;; +Entrypoint ; $Cn0E - entrypoint? + bcs GetSlotInX ; C=1: GetSlotInX -> cmdproc -Bootstrap -; Lets check to see if there's an image ready - lda #$00 - sta hd_command - -; Slot 7, disk 1 - lda #$70 ; Slot# << 4 - sta hd_unitnum - lda hd_execute - -; error capturing code. Applewin is picky -; about code assigning data to registers and -; memory. The safest method is via I/O port - ror hd_error ; Post: C=0 or 1 - bcc hdboot - -; no image ready, boot diskette image instead -BootSlot6 - jmp slot6 + ; C=0: fall through to SmartPort... ;====================================== ; TODO: Is it OK to trash Y and $42,..,$47 ? -; Pre: C=0 -SmartPort +; Pre: C=0, X = Slot# << 4 +SmartPort ; SmartPort -> GetSlotInX -> cmdproc pla sta $46 adc #3 ; Pre: C=0, Post: C=0 or 1 @@ -136,24 +117,7 @@ SmartPort lda ($46),y ; cmd sta $42 iny - bne SmartPort2 -;====================================== -; 8 unused bytes - -*= $c746 ; org $c746 - -Entrypoint_C746 ; Old f/w 'cmdproc' entrypoint - ; Keep this for any DOSMaster HDD images created with old AppleWin HDD f/w. - ; DOSMaster hardcodes the entrypoint addr into its bootstrapping code: - ; - So DOSMaster images are tied to the HDD's controller's f/w - sec - bcs Entrypoint - -;====================================== - -; Pre: Y=2 -SmartPort2 lda ($46),y ; param_l sta $45 iny @@ -164,31 +128,76 @@ SmartPort2 lda ($45),y ; unit sta $43 iny + lda ($45),y ; memblock_l sta $44 iny - bne SmartPort3 + lda ($45),y ; memblock_h + pha + iny + + lda ($45),y ; diskblock_l + pha + iny + + bne SmartPort2 ;====================================== -; 1 unused byte - -*= $c761 ; org $c761 +; 2 unused bytes -Entrypoint_C761 ; Apple Oasis HDD controller entrypoint +@checkCs46 +*= $0046 ; org $0046 +!warn "Cs46 padding = ", * - @checkCs46 + +Entrypoint_Cs46 ; Old f/w 'cmdproc' entrypoint + ; Keep this for any DOSMaster HDD images created with old AppleWin HDD f/w. + ; DOSMaster hardcodes the entrypoint addr into its bootstrapping code: + ; - So DOSMaster images are tied to the HDD's controller's f/w + sec + bcs Entrypoint ; or directly to GetSlotInX + +;====================================== + +Bootstrap +; Lets check to see if there's an image ready +; Slot n, disk 1 + clc + bcc GetSlotInX ; Post: X = Slot# << 4 +Bootstrap2 + lda #$00 + sta hd_unitnum,x ; b7=0 => disk 1 + sta hd_command,x + lda hd_execute,x + ror hd_error,x ; Post: C=0 or 1 + bcc hdboot + +; no image ready, boot diskette image instead +BootSlot6 + jmp slot6 + +;====================================== +; 2 unused bytes + +@checkCs61 +*= $0061 ; org $0061 +!warn "Cs61 padding = ", * - @checkCs61 + +Entrypoint_Cs61 ; Apple Oasis HDD controller entrypoint ; Keep this for any DOSMaster HDD images created with Apple Oasis HDD f/w. ; DOSMaster hardcodes the entrypoint addr into its bootstrapping code: ; - So DOSMaster images are tied to the HDD's controller's f/w sec - bcs Entrypoint + bcs Entrypoint ; or directly to GetSlotInX ;====================================== ; image ready. Lets boot from it. -; we want to load block 1 from s7,d1 to $800 then jump there +; we want to load block 1 from disk 1 to $800 then jump there +; Pre: X = Slot# << 4 +; C = 0 hdboot - lda #$70 ; Slot# << 4 - sta unitnum lda #$0 + sta unitnum ; b7=0 => disk 1 sta memblock sta diskblock sta diskblock+1 @@ -196,101 +205,109 @@ hdboot sta memblock+1 lda #$1 sta command - jsr cmdproc + bne cmdproc +hdboot2 bcs BootSlot6 -goload - bit BUTTON0 ; button 0 pressed? + bit BUTTON0 ; button 0 pressed? bmi BootSlot6 -; X=device - ldx #$70 ; Slot# << 4 +; Pre: X = Slot# << 4 jmp OS -; entry point for ProDOS' block driver -; simple really. Copy the command from $42..$47 -; to our I/O ports then execute command -cmdproc - clc - lda $42 - sta hd_command - lda $43 - sta hd_unitnum - lda $44 - sta hd_memblock - lda $45 - sta hd_memblock+1 - lda $46 - sta hd_diskblock - lda $47 - sta hd_diskblock+1 - lda hd_execute - -; check for error - pha - lda command - cmp #1 - bne skipSread - jsr sread -skipSread - ror hd_error ; Post: C=0 or 1 - pla - rts - - -; if there's no error, then lets read the block into memory -; because Applewin is picky about memory management, here's what I did: -; on read, hd_nextbyte = buffer[0], therefore we'll read that byte 256 times (in which -; the emulated code increments the buffer by 1 on each read) to (memblock),y -; increment memblock+1 and read the second 256 bytes via hd_nextbyte. -; -; if I could figure out how to consistently get applewin to update it's memory regions all -; this code can be moved into the emulation code (although, this is how I'd build the hardware -; anyway...) - -sread - tya - pha - ldy #0 -loop1 - lda hd_nextbyte - sta (memblock),y - iny - bne loop1 - inc memblock+1 - ldy #0 -loop2 - lda hd_nextbyte - sta (memblock),y - iny - bne loop2 - dec memblock+1 ; restore memblock+1 ($45) to original value (for Epyx's California Games) - pla - tay - rts - ;====================================== -SmartPort3 - lda ($45),y ; memblock_h - pha - iny - lda ($45),y ; diskblock_l - pha - iny +SmartPort2 lda ($45),y ; diskblock_h sta $47 - + pla sta $46 pla sta $45 - - iny - bne cmdproc + + sec + ; fall through... ;====================================== -; 18 unused bytes + +; Pre: +; C=0 => via Bootstrap +; C=1 => via Entrypoint / SmartPort2 +; Post: +; X = Slot# << 4 +GetSlotInX + php + sei ; disable ints, in case an int handler races our $0000/RTS and stack accesses! + + ; NB. need RAM that's guaranteed to be both read & writeable: + ; . can't use $0200-$BFFF, due to eg. RAMRD=0/RAMWRT=1 combination + ; . can't use LC as ROM might be enabled. + ; So use ZP (specifically $0000) as whatever the state of ALTZP, both read & write will be to the same physical memory location. + lda $00 ; save $00 + ldx #$60 ; opcode RTS + stx $00 + jsr $0000 ; RTS immediately (NB. can't use $FF58, since LC RAM may be switched in) + sta $00 ; restore $00 + tsx + lda $0100,x ; $Cn + asl + asl + asl + asl + tax ; X=$n0 + + plp ; + restore int status + bcc Bootstrap2 + ; otherwise fall through for Entrypoint / SmartPort... + +;-------------------------------------- + +; entry point for ProDOS' block driver +; simple really. Copy the command from $42..$47 +; to our I/O ports then execute command + +; Pre: +; C=0 => via Bootstrap (hdboot) +; C=1 => via GetSlotInX (eg. Entrypoint / SmartPort2) +; X = Slot# << 4 +; Post: +; C = hd_error.b0 +; A = result of hd_execute +; X = Slot# << 4 +cmdproc + php + + lda command + sta hd_command,x + lda unitnum + sta hd_unitnum,x + lda memblock + sta hd_memblock,x + lda memblock+1 + sta hd_memblock+1,x + lda diskblock + sta hd_diskblock,x + lda diskblock+1 + sta hd_diskblock+1,x + lda hd_execute,x ; A = result of hd_execute (NB. instantaneous 512 byte r/w!) + +- rol hd_error,x ; b7=busy doing DMA? + bcs - + + plp ; restore C from start of cmdproc + bcs done + ror hd_error,x ; Post: C=0 or 1 + lda #0 + beq hdboot2 + +done + ror hd_error,x ; Post: C=0 or 1 + rts + +;====================================== + +; 33 unused bytes !zone data @@ -309,7 +326,10 @@ SmartPort3 ; datablock. This starts near the end of the firmware (at offset $FC) ;; data -*= $c7fc ; org $c7fc +@checkCsFC +*= $00FC ; org $00FC +!warn "CsFC padding = ", * - @checkCsFC + !word $7fff ; how many blocks are on the device. !byte $D7 ; specifics about the device (number of drives, read/write/format capability, etc) !byte Disk, then MemInitializeIO() won't be called @@ -190,7 +193,20 @@ void HarddiskInterfaceCard::CleanupDrive(const int iDrive) void HarddiskInterfaceCard::NotifyInvalidImage(TCHAR* pszImageFilename) { - // TC: TO DO + // TC: TO DO - see Disk2InterfaceCard::NotifyInvalidImage() + + char szBuffer[MAX_PATH + 128]; + + StringCbPrintf( + szBuffer, + MAX_PATH + 128, + TEXT("Unable to open the file %s."), + pszImageFilename); + + GetFrame().FrameMessageBox( + szBuffer, + g_pAppTitle.c_str(), + MB_ICONEXCLAMATION | MB_SETFOREGROUND); } //=========================================================================== @@ -206,11 +222,17 @@ void HarddiskInterfaceCard::LoadLastDiskImage(const int drive) char pathname[MAX_PATH]; std::string& regSection = RegGetConfigSlotSection(m_slot); - if (RegLoadString(regSection.c_str(), regKey.c_str(), TRUE, pathname, MAX_PATH, TEXT(""))) + if (RegLoadString(regSection.c_str(), regKey.c_str(), TRUE, pathname, MAX_PATH, TEXT("")) && (pathname[0] != 0)) { m_saveDiskImage = false; - Insert(drive, pathname); + bool res = Insert(drive, pathname); m_saveDiskImage = true; + + if (!res) + { + NotifyInvalidImage(pathname); + CleanupDrive(drive); + } } } @@ -309,10 +331,10 @@ void HarddiskInterfaceCard::Destroy(void) //=========================================================================== // Pre: pathname likely to include path (but can also just be filename) -BOOL HarddiskInterfaceCard::Insert(const int iDrive, const std::string& pathname) +bool HarddiskInterfaceCard::Insert(const int iDrive, const std::string& pathname) { if (pathname.empty()) - return FALSE; + return false; if (m_hardDiskDrive[iDrive].m_imageloaded) Unplug(iDrive); @@ -429,8 +451,8 @@ bool HarddiskInterfaceCard::IsDriveUnplugged(const int iDrive) //=========================================================================== #define DEVICE_OK 0x00 -#define DEVICE_UNKNOWN_ERROR 0x28 #define DEVICE_IO_ERROR 0x27 +#define DEVICE_NOT_CONNECTED 0x28 // No device detected/connected BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles) { @@ -438,6 +460,9 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY HarddiskInterfaceCard* pCard = (HarddiskInterfaceCard*)MemGetSlotParameters(slot); HardDiskDrive* pHDD = &(pCard->m_hardDiskDrive[pCard->m_unitNum >> 7]); // bit7 = drive select + CpuCalcCycles(nExecutedCycles); + const UINT CYCLES_FOR_DMA_RW_BLOCK = HD_BLOCK_SIZE; + BYTE r = DEVICE_OK; pHDD->m_status_next = DISK_STATUS_READ; @@ -466,7 +491,31 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY { pHDD->m_error = 0; r = 0; + pCard->m_notBusyCycle = g_nCumulativeCycles + (UINT64)CYCLES_FOR_DMA_RW_BLOCK; pHDD->m_buf_ptr = 0; + + // Apple II's MMU could be setup so that read & write memory is different, + // so can't use 'mem' (like we can for HDD block writes) + const UINT PAGE_SIZE = 256; + WORD dstAddr = pHDD->m_memblock; + UINT remaining = HD_BLOCK_SIZE; + BYTE* pSrc = pHDD->m_buf; + + while (remaining) + { + memdirty[dstAddr >> 8] = 0xFF; + LPBYTE page = memwrite[dstAddr >> 8]; + + // handle both page-aligned & non-page aligned destinations + UINT size = PAGE_SIZE - (dstAddr & 0xff); + if (size > remaining) size = remaining; // clip the last memcpy for the unaligned case + + memcpy(page + (dstAddr & 0xff), pSrc, size); + pSrc += size; + dstAddr += size; + + remaining -= size; + } } else { @@ -501,7 +550,7 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY } } - memmove(pHDD->m_buf, mem+pHDD->m_memblock, HD_BLOCK_SIZE); + memcpy(pHDD->m_buf, mem + pHDD->m_memblock, HD_BLOCK_SIZE); if (bRes) bRes = ImageWriteBlock(pHDD->m_imagehandle, pHDD->m_diskblock, pHDD->m_buf); @@ -510,6 +559,7 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY { pHDD->m_error = 0; r = 0; + pCard->m_notBusyCycle = g_nCumulativeCycles + (UINT64)CYCLES_FOR_DMA_RW_BLOCK; } else { @@ -527,16 +577,19 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY { pHDD->m_status_next = DISK_STATUS_OFF; pHDD->m_error = 1; - r = DEVICE_UNKNOWN_ERROR; + r = DEVICE_NOT_CONNECTED; // GH#452 } break; case 0x1: // m_error - pHDD->m_status_next = DISK_STATUS_OFF; // TODO: FIXME: ??? YELLOW ??? WARNING - if (pHDD->m_error) - { - _ASSERT(pHDD->m_error & 1); - pHDD->m_error |= 1; // Firmware requires that b0=1 for an error - } + if (pHDD->m_error & 0x7f) + pHDD->m_error = 1; // Firmware requires that b0=1 for an error + else + pHDD->m_error = 0; + + if (g_nCumulativeCycles <= pCard->m_notBusyCycle) + pHDD->m_error |= 0x80; // Firmware requires that b7=1 for busy (eg. busy doing r/w DMA operation) + else + pHDD->m_status_next = DISK_STATUS_OFF; // TODO: FIXME: ??? YELLOW ??? WARNING r = pHDD->m_error; break; @@ -558,7 +611,7 @@ BYTE __stdcall HarddiskInterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BY case 0x7: r = (BYTE)(pHDD->m_diskblock & 0xFF00 >> 8); break; - case 0x8: + case 0x8: // Legacy: continue to support this I/O port for old HDD firmware r = pHDD->m_buf[pHDD->m_buf_ptr]; if (pHDD->m_buf_ptr < sizeof(pHDD->m_buf)-1) pHDD->m_buf_ptr++; @@ -649,8 +702,10 @@ bool HarddiskInterfaceCard::ImageSwap(void) //=========================================================================== // Unit version history: -// 2: Updated $Csnn firmware to fix GH#319 -static const UINT kUNIT_VERSION = 2; +// 2: Updated $C7nn firmware to fix GH#319 +// 3: Updated $Csnn firmware to fix GH#996 (now slot-independent code) +// Added: Not Busy Cycle +static const UINT kUNIT_VERSION = 3; #define SS_YAML_VALUE_CARD_HDD "Generic HDD" @@ -667,6 +722,7 @@ static const UINT kUNIT_VERSION = 2; #define SS_YAML_KEY_STATUS_PREV "Status Prev" #define SS_YAML_KEY_BUF_PTR "Buffer Offset" #define SS_YAML_KEY_BUF "Buffer" +#define SS_YAML_KEY_NOT_BUSY_CYCLE "Not Busy Cycle" std::string HarddiskInterfaceCard::GetSnapshotCardName(void) { @@ -700,6 +756,7 @@ void HarddiskInterfaceCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); yamlSaveHelper.Save("%s: %d # b7=unit\n", SS_YAML_KEY_CURRENT_UNIT, m_unitNum); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_COMMAND, m_command); + yamlSaveHelper.SaveHexUint64(SS_YAML_KEY_NOT_BUSY_CYCLE, m_notBusyCycle); SaveSnapshotHDDUnit(yamlSaveHelper, HARDDISK_1); SaveSnapshotHDDUnit(yamlSaveHelper, HARDDISK_2); @@ -777,12 +834,15 @@ bool HarddiskInterfaceCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT sl if (version < 1 || version > kUNIT_VERSION) throw std::string("Card: wrong version"); - if (version == 1 && (regs.pc >> 8) == (0xC0|slot)) + if (version <= 2 && (regs.pc >> 8) == (0xC0|slot)) throw std::string("HDD card: 6502 is running old HDD firmware"); m_unitNum = yamlLoadHelper.LoadUint(SS_YAML_KEY_CURRENT_UNIT); // b7=unit m_command = yamlLoadHelper.LoadUint(SS_YAML_KEY_COMMAND); + if (version >= 3) + m_notBusyCycle = yamlLoadHelper.LoadUint64(SS_YAML_KEY_NOT_BUSY_CYCLE); + // Unplug all HDDs first in case HDD-2 is to be plugged in as HDD-1 for (UINT i=0; i