Slot-independent HDD firmware (#996, PR #998)

. Move the 'read block' command into the emulator - to mirror the write command
. With emulated time to do the DMA for the r/w block command
This commit is contained in:
TomCh 2021-11-09 21:04:57 +00:00 committed by GitHub
parent d96ed5b8c5
commit 5ed901f720
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 259 additions and 172 deletions

Binary file not shown.

View File

@ -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 <Entrypoint_ProDOS ; entry point offset for ProDOS (must be $0a)

Binary file not shown.

View File

@ -178,11 +178,17 @@ void Disk2InterfaceCard::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;
InsertDisk(drive, pathname, IMAGE_USE_FILES_WRITE_PROTECT_STATUS, IMAGE_DONT_CREATE);
ImageError_e error = InsertDisk(drive, pathname, IMAGE_USE_FILES_WRITE_PROTECT_STATUS, IMAGE_DONT_CREATE);
m_saveDiskImage = true;
if (error != eIMAGE_ERROR_NONE)
{
NotifyInvalidImage(drive, pathname, error);
EjectDisk(drive);
}
}
}

View File

@ -125,6 +125,9 @@ HarddiskInterfaceCard::HarddiskInterfaceCard(UINT slot) :
// . ProDOS will write to Command before switching drives
m_command = 0;
// Interface busy doing DMA for r/w when current cycle is earlier than this cycle
m_notBusyCycle = 0;
m_saveDiskImage = true; // Save the DiskImage name to Registry
// if created by user in Config->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<NUM_HARDDISKS; i++)
{

View File

@ -94,7 +94,7 @@ public:
const std::string& HarddiskGetFullPathName(const int iDrive);
void GetFilenameAndPathForSaveState(std::string& filename, std::string& path);
bool Select(const int iDrive);
BOOL Insert(const int iDrive, const std::string& pathname);
bool Insert(const int iDrive, const std::string& pathname);
void Unplug(const int iDrive);
bool IsDriveUnplugged(const int iDrive);
void LoadLastDiskImage(const int iDrive);
@ -125,6 +125,7 @@ private:
BYTE m_unitNum; // b7=unit
BYTE m_command;
UINT64 m_notBusyCycle;
bool m_saveDiskImage; // Save the DiskImage name to Registry