;CFIDE.V11A
          .TITLE    "Apple /// CompactFlash/IDE Driver Ver 1.1"

          .PROC     CFIDE

                    ; Apple /// CF-IDE Hard Disk Driver
                    ; SOS Equates
Extpg     .EQU      1401             ;driver extended bank address offset
AllocSIR  .EQU      1913             ;allocate system internal resource
DeAlcSIR  .EQU      1916             ;de-allocate system internal resource
SysErr    .EQU      1928             ;report error to system
EReg      .EQU      0FFDF            ;environment register

ReqCode   .EQU      0C0              ;request code
SosUnit   .EQU      0C1              ;unit number
SosBuf    .EQU      0C2              ;buffer pointer
ReqCnt    .EQU      0C4              ;requested byte count
CtlStat   .EQU      0C2              ;control/status code
CSList    .EQU      0C3              ;control/status list pointer
SosBlk    .EQU      0C6              ;starting block number
QtyRead   .EQU      0C8              ;bytes read return by D_READ

                    ; Our temps in zero page
CurPart   .EQU      00CC
Count     .EQU      00CD             ;2 bytes

                    ;Parameter block specific to current SOS request
ATA_Cmd   .EQU      00CF
Drv_Parm  .EQU      00D0
Sect_HB   .EQU      00D1
Sect_MB   .EQU      00D2
Sect_LB   .EQU      00D3
Num_Blks  .EQU      00D4             ;2 bytes lb,hb
Buffer    .EQU      00D6             ;2 bytes
CurDrive  .EQU      00D8

                    ; SOS Error Codes
XREQCODE  .EQU      020              ;Invalid request code
XCTLCODE  .EQU      021              ;Invalid control/status code
XCTLPARAM .EQU      022              ;Invalid control/status parameter
XNORESRC  .EQU      025              ;Resource not available
XBADOP    .EQU      026              ;Invalid operation
XIOERROR  .EQU      027              ;I/O error
XNODRIVE  .EQU      028              ;Drive not connected
XBYTECNT  .EQU      02C              ;Byte count not a multiple of 512
XBLKNUM   .EQU      02D              ;Block number to large
XDRIVERR  .EQU      030              ;device specific error occurred

                    ;IFC I/O locations
IOBase    .EQU      0C080
ATdataHB  .EQU      IOBase+0.
SetCSMsk  .EQU      IOBase+1.        ;Two special strobe locations to set and clear
                                     ;MASK bit that is used to disable CS0 line to
                                     ;the CompactFlash during the CPU read cycles
ClrCSMsk  .EQU      IOBase+2.        ;that occur before every CPU write cycle.
                                     ;The normally innocuous read cycles were
                                     ;causing the SanDisk CF to double increment
                                     ;during sector writes commands.

Alt_Stat  .EQU      IOBase+6.        ;when reading (not used)
ATAdCtrl  .EQU      IOBase+6.        ;when writing
ATdataLB  .EQU      IOBase+8.
ATAError  .EQU      IOBase+9.
ATSectCt  .EQU      IOBase+10.
ATSector  .EQU      IOBase+11.       ;11=LB,12=MB,13=HB
ATAHead   .EQU      IOBase+14.
ATCmdReg  .EQU      IOBase+15.       ;when writing
ATA_Stat  .EQU      IOBase+15.       ;when reading

                    ; ATA Commands Codes
ATA_XErr  .EQU      003
ATACRead  .EQU      020
ATACWrit  .EQU      030
ATA_Vrfy  .EQU      040
ATA_Frmt  .EQU      050
ATA_Diag  .EQU      090
ATAIdent  .EQU      0EC

                    ;Constants for Wait
                    ;Constant =  (Delay[in uS]/2.5 + 2.09)^.5 - 2.7
Wait100m  .EQU      197.
WAIT10ms  .EQU      61.
Wait29us  .EQU      1.

                    ; Switch Macro
          .MACRO    switch
          .IF       "%1" <> ""       ;if parameter 1 is present
          LDA       %1               ;load A with switch index
          .ENDC
          CMP       #%2+1            ;do bounds check
          BCS       $010
          ASL    A
          TAY
          LDA       %3+1,Y           ;get switch index from table
          PHA
          LDA       %3,Y
          PHA
          .IF       "%4" <> "*"      ;if parameter 4 omitted,
          RTS       ;go to code
          .ENDC
$010      .ENDM

                    ; Device identification Block (DIB) - Volume #0
DIB_0     .WORD     DIB_1            ;link pointer
          .WORD     Entry            ;entry pointer
          .BYTE     7                ;name length byte
          .ASCII    ".CFIDE1        ";device name

          .BYTE     080              ;active, no page alignment
DIB0_Slot .BYTE     0FF              ;slot number
          .BYTE     000              ;unit number
          .BYTE     0D1              ;type
          .BYTE     010              ;subtype
          .BYTE     000              ;filler
DIB0_Blks .WORD     0000             ;# blocks in device
          .WORD     444A             ;manufacturer - DJ
          .WORD     1100             ;Version 1.1
          .WORD     0002             ;DCB length followed by DCB
Driv_No0  .BYTE     000              ;Drive number = $00 (master) or $01 (slave)
Part_No0  .BYTE     000              ;Partition number on the drive = $00-07

                    ; Device identification Block (DIB) - Volume #1
DIB_1     .WORD     DIB_2            ;link pointer
          .WORD     Entry            ;entry pointer
          .BYTE     7                ;name length byte
          .ASCII    ".CFIDE2        ";device name

          .BYTE     080              ;active, no page alignment
          .BYTE     000              ;slot number
          .BYTE     001              ;unit number
          .BYTE     0D1              ;type
          .BYTE     010              ;subtype
          .BYTE     000              ;filler
DIB1_Blks .WORD     0000             ;# blocks in device
          .WORD     444A             ;manufacturer - DJ
          .WORD     1100             ;Version 1.1
          .WORD     0002             ;DCB length followed by DCB
          .BYTE     000              ;Drive number = $00 (master) or $01 (slave)
          .BYTE     001              ;Partition number on the drive = $00-07

                    ; Device identification Block (DIB) - Volume #2
DIB_2     .WORD     DIB_3            ;link pointer
          .WORD     Entry            ;entry pointer
          .BYTE     7                ;name length byte
          .ASCII    ".CFIDE3        ";device name

          .BYTE     080              ;active, no page alignment
          .BYTE     000              ;slot number
          .BYTE     002              ;unit number
          .BYTE     0D1              ;type
          .BYTE     010              ;subtype
          .BYTE     000              ;filler
DIB2_Blks .WORD     0000             ;# blocks in device
          .WORD     444A             ;manufacturer - DJ
          .WORD     1100             ;Version 1.1
          .WORD     0002             ;DCB length followed by DCB
          .BYTE     000              ;Drive number = $00 (master) or $01 (slave)
          .BYTE     002              ;Partition number on the drive = $00-07

                    ; Device identification Block (DIB) - Volume #3
DIB_3     .WORD     DIB_4            ;link pointer
          .WORD     Entry            ;entry pointer
          .BYTE     7                ;name length byte
          .ASCII    ".CFIDE4        ";device name

          .BYTE     080              ;active, no page alignment
          .BYTE     000              ;slot number
          .BYTE     003              ;unit number
          .BYTE     0D1              ;type
          .BYTE     010              ;subtype
          .BYTE     000              ;filler
DIB3_Blks .WORD     0000             ;# blocks in device
          .WORD     444A             ;manufacturer - DJ
          .WORD     1100             ;Version 1.1
          .WORD     0002             ;DCB length followed by DCB
          .BYTE     000              ;Drive number = $00 (master) or $01 (slave)
          .BYTE     003              ;Partition number on the drive = $00-07

                    ; Device identification Block (DIB) - Volume #4
DIB_4     .WORD     DIB_5            ;link pointer
          .WORD     Entry            ;entry pointer
          .BYTE     7                ;name length byte
          .ASCII    ".CFIDE5        ";device name

          .BYTE     080              ;active, no page alignment
          .BYTE     000              ;slot number
          .BYTE     004              ;unit number
          .BYTE     0D1              ;type
          .BYTE     010              ;subtype
          .BYTE     000              ;filler
DIB4_Blks .WORD     0000             ;# blocks in device
          .WORD     444A             ;manufacturer - DJ
          .WORD     1100             ;Version 1.1
          .WORD     0002             ;DCB length followed by DCB
          .BYTE     000              ;Drive number = $00 (master) or $01 (slave)
          .BYTE     004              ;Partition number on the drive = $00-07

                    ; Device identification Block (DIB) - Volume #5
DIB_5     .WORD     DIB_6            ;link pointer
          .WORD     Entry            ;entry pointer
          .BYTE     7                ;name length byte
          .ASCII    ".CFIDE6        ";device name

          .BYTE     080              ;active, no page alignment
          .BYTE     000              ;slot number
          .BYTE     005              ;unit number
          .BYTE     0D1              ;type
          .BYTE     010              ;subtype
          .BYTE     000              ;filler
DIB5_Blks .WORD     0000             ;# blocks in device
          .WORD     444A             ;manufacturer - DJ
          .WORD     1100             ;Version 1.1
          .WORD     0002             ;DCB length followed by DCB
          .BYTE     000              ;Drive number = $00 (master) or $01 (slave)
          .BYTE     005              ;Partition number on the drive = $00-07

                    ; Device identification Block (DIB) - Volume #6
DIB_6     .WORD     DIB_7            ;link pointer
          .WORD     Entry            ;entry pointer
          .BYTE     7                ;name length byte
          .ASCII    ".CFIDE7        ";device name

          .BYTE     080              ;active, no page alignment
          .BYTE     000              ;slot number
          .BYTE     006              ;unit number
          .BYTE     0D1              ;type
          .BYTE     010              ;subtype
          .BYTE     000              ;filler
DIB6_Blks .WORD     0000             ;# blocks in device
          .WORD     444A             ;manufacturer - DJ
          .WORD     1100             ;Version 1.1
          .WORD     0002             ;DCB length followed by DCB
          .BYTE     000              ;Drive number = $00 (master) or $01 (slave)
          .BYTE     006              ;Partition number on the drive = $00-07

                    ; Device identification Block (DIB) - Volume #7
DIB_7     .WORD     0000             ;link pointer
          .WORD     Entry            ;entry pointer
          .BYTE     7                ;name length byte
          .ASCII    ".CFIDE8        ";device name

          .BYTE     080              ;active, no page alignment
          .BYTE     000              ;slot number
          .BYTE     007              ;unit number
          .BYTE     0D1              ;type
          .BYTE     010              ;subtype
          .BYTE     000              ;filler
DIB7_Blks .WORD     0000             ;# blocks in device
          .WORD     444A             ;manufacturer - DJ
          .WORD     1100             ;Version 1.1
          .WORD     0002             ;DCB length followed by DCB
          .BYTE     000              ;Drive number = $00 (master) or $01 (slave)
          .BYTE     007              ;Partition number on the drive = $00-07

          .ASCII    "Written By Dale S. Jackson, initial writing 12/12/02,"
          .ASCII    "revised version 1.1    8/13/2004"

                    ; local storage locations
SlotCX    .BYTE     000              ;compute C0X0 and store on init
PartStat  .BYTE     0FF,0FF          ;Drive $0,$1  partition table status
                                     ;High bit set = Partition map not current

PtBlkIdx  .BYTE     0A3,0A8,0B3,0B8,0C3,0C8,0D3,0D8  ;Offsets to beginning 8 block
PtVolIdx  .BYTE     0A6,0AB,0B6,0BB,0C6,0CB,0D6,0DB  ;segment for each partition

UnitStat  .BLOCK    008,XNODRIVE     ;if > $0F then UnitStat = error code
                                     ;else UnitStat = partition number
LastOP    .BLOCK    008,0FF          ;last op for D_REPEAT calls

StBlk_HB  .BLOCK    008,000          ;Starting block number for SOS/ProDos
StBlk_MB  .BLOCK    008,000          ;vol #0, vol #1, vol #2, vol #3
StBlk_LB  .BLOCK    008,000          ;vol #4, vol #5, vol #6, vol #7

Block_LB  .BLOCK    008,000          ;Total blocks for each volume #
Block_HB  .BLOCK    008,000

DIB_Idx   .BYTE     000
          .BYTE     DIB1_Blks-DIB0_Blks
          .BYTE     DIB2_Blks-DIB0_Blks
          .BYTE     DIB3_Blks-DIB0_Blks
          .BYTE     DIB4_Blks-DIB0_Blks
          .BYTE     DIB5_Blks-DIB0_Blks
          .BYTE     DIB6_Blks-DIB0_Blks
          .BYTE     DIB7_Blks-DIB0_Blks

SIR_Addr  .WORD     SIR_Tbl
SIR_Tbl   .BLOCK    005,000
SIR_Len   .EQU      *-SIR_Tbl

PmapCall  .BLOCK    003,000          ;Block $00 00 00 for partition record sector
          .BYTE     001,000          ;Read 1 ATA sector (block) lb, hb
          .WORD     PmapBuf          ;Buffer address to place partition record

PmapBuf   .BLOCK    0100,000
Err_Data  .BLOCK    0100,000

                    ; Main entry point
Entry     LDA       UnitStat+0
          CMP       #XNORESRC
          BEQ       Err_Out1

          LDX       SosUnit          ;get drive number for this unit
          LDY       DIB_Idx,X
          LDA       Driv_No0,Y
          CMP       #002
          BCS       BadDrive
          STA       CurDrive         ;Set device to LBA mode
          ORA       #00E             ;device mode bits   %00001(LBA)1(Drive#)
          ASL       A                ;shift left 4 bits to high order nibble
          ASL       A
          ASL       A
          ASL       A
          STA       Drv_Parm
          LDA       ReqCode
          CMP       #002             ;Status Call
          BCS       $1
          JSR       InitPmap         ;Check if partition table is current - fetch it if not.
$1        JSR       Dispatch         ;Now call the dispatcher
          LDX       SosUnit          ;Save current operation
          LDA       ReqCode          ;for D_REPEAT processing
          STA       LastOP,X
          RTS       ;Bye

                    ; The Dispatcher.  Does it depending on ReqCode.  Note that if we came in on
                    ; a D_INIT call, we do a branch to Dispatch, normally.  Dispatch is called as
                    ; a subroutine!  We copy the buffer pointer and block # from the parameter
                    ; area into our own temps, as the system seems to want them left ALONE.

Dispatch  SWITCH    ReqCode,9,DoTable  ;go do it.

BadReq    LDA       #XREQCODE        ;bad request code!
Err_Out1  JSR       SysErr

BadOp     LDA       #XBADOP          ;invalid operation!
          BNE       Err_Out1
BadDrive  LDA       #XNODRIVE        ;invalid drive!
          BNE       Err_Out1


                    ; Dispatch table.  One entry per command number, with holes.
DoTable   .WORD     DRead-1          ;0 read request
          .WORD     DWrite-1         ;1 write request
          .WORD     DStatus-1        ;2 status request
          .WORD     DControl-1       ;3 control request
          .WORD     BadReq-1         ;4 unused
          .WORD     BadReq-1         ;5 unused
          .WORD     BadOp-1          ;6 open - invalid request
          .WORD     BadOp-1          ;7 close - invalid request
          .WORD     DInit-1          ;8 init request
          .WORD     DRepeat-1        ;9 repeat request

                    ; Processing D_REPEAT - repeat the last D_READ or D_WRITE call
DRepeat   LDX       SosUnit
          LDA       LastOP,X         ;look at the last thing we did
          CMP       #002
          BCS       BadOp
          STA       ReqCode
          JMP       Dispatch

                    ; D_INIT call processing for all Volumes.
                    ; Called at system init time only.  Check DIB0_Slot to make sure that the user
                    ; set a valid slot number for our interface.  Allocate it by calling AllocSIR.
                    ; If slot not available then set UnitStat+0 to XNORESRC error code.
DInit     LDA       SIR_Tbl
          BNE       Norm_Out
          LDA       DIB0_Slot
          AND       #007

                    ; Compute the system internal resource number (SIR) and call AllocSIR to
                    ; try and grab that for us.  It performs slot checking as a side effect.
          ORA       #010             ;SIR = 16+slot#
          STA       SIR_Tbl
          LDA       #SIR_Len
          LDX       SIR_Addr
          LDY       SIR_Addr+1
          JSR       AllocSIR         ;this one's mine!
          BCS       Slot_Err

          LDA       DIB0_Slot        ;Compute C0X0 for this slot
          ASL   A
          ASL   A
          ASL   A
          ASL   A
          STA       SlotCX
          JSR       ResetDev         ;Initialization of Device
          BCC       $1
          LDA       #SIR_Len
          LDX       SIR_Addr
          LDY       SIR_Addr+1
          JSR       DeAlcSIR         ;Return resource to SOS
          JMP       Slot_Err
$1        LDY       #007             ;Reset drive status to initial startup
          LDA       #XNODRIVE
$2        STA       UnitStat,Y
          DEY
          BPL       $2
          LDA       #0FF
          STA       PartStat
          STA       PartStat+1

Norm_Out  CLC
          RTS

          .INCLUDE  CFIDE.V11B
          .END

;CFIDE.V11B
                    ; Initialize Partition Table Data
InitPmap  LDX       CurDrive
          LDA       PartStat,X
          BPL       $1
          JSR       GetPmap
          JSR       PInit
$1        LDX       SosUnit          ;Check if Unit driver is good.
          LDA       UnitStat,X
          CMP       #010
          BCS       Err_Out2
          RTS

Slot_Err  LDA       #XNORESRC        ;SIR not available
          STA       UnitStat+0       ;no! it didn't go ok.
Err_Out2  JMP       SysErr           ;doesn't return

GetPmap   LDY       #006
$1        LDA       PmapCall,Y
          STA       Sect_HB,Y
          DEY
          BPL       $1
          STA       Buffer+Extpg     ;$00
          LDA       #ATACRead
          STA       ATA_Cmd
          JMP       SRead

                    ;Initialize partition information for each partition
PInit     LDX       #000
$1        LDY       DIB_Idx,X
          LDA       CurDrive
          CMP       Driv_No0,Y
          BNE       $4
          LDA       Part_No0,Y    ;Get assigned partition number for this driver
          CMP       #008
          BCS       $2            ;Partition number is out of range
          TAY
          STY       CurPart
          JSR       GStrtBlk
          BCS       $2
          JSR       GVolSize
          BCS       $2
          LDY       DIB_Idx,X
          LDA       Block_HB,X
          STA       DIB0_Blks+1,Y
          LDA       Block_LB,X
          STA       DIB0_Blks,Y
          LDY       CurDrive
          LDA       CurPart
          STA       PartStat,Y
          BPL       $3
$2        LDA       #XNODRIVE      ;Flag this driver not useable
$3        STA       UnitStat,X
$4        INX
          CPX       #008
          BCC       $1
          CLC
          RTS

GStrtBlk  LDA       PtBlkIdx,Y       ;Xreg contains DIB unit number,
          TAY                        ;Yreg contains drive partition count
          LDA       PmapBuf+2,Y
          CMP       #0FF             ;Test if beginning track is valid
          BNE       $1
          CMP       PmapBuf+1,Y
          BNE       $1
          CMP       PmapBuf,Y
          BEQ       Bad_Ptn          ;Return with carry set
$1        STA       StBlk_HB,X
          LDA       PmapBuf+1,Y
          STA       StBlk_MB,X
          ORA       PmapBuf+2,Y      ;Check if beginning track is zero
          ORA       PmapBuf,Y
          BEQ       Bad_Ptn          ;Return with carry set if zero
          LDA       PmapBuf,Y
          STA       StBlk_LB,X
          CLC
          RTS
Bad_Ptn   SEC
          RTS

GVolSize  LDY       CurPart          ;Xreg contains DIB unit number
          LDA       PtVolIdx,Y
          TAY
          LDA       PmapBuf,Y
          STA       Block_LB,X
          ORA       PmapBuf+1,Y
          BEQ       Bad_Ptn          ;Volume size cannot be zero
          LDA       PmapBuf+1,Y
          STA       Block_HB,X
          CLC
          LDA       StBlk_LB,X       ;Check if maximum addressable
          ADC       Block_LB,X       ;blocks is exceeded
          LDA       StBlk_MB,X
          ADC       Block_HB,X
          LDA       StBlk_HB,X
          ADC       #000
          CMP       #008
          RTS

                    ; Random support and checking routines for the block driver
                    ; Check ReqCnt to insure it's a multiple of 512.

CkCnt     LDA       ReqCnt           ;look at the lsb of bytes to do
          BNE       $1               ;no good!  lsb should be 00
          STA       Num_Blks+1       ;zero high byte of number of blocks
          LDA       ReqCnt+1         ;look at the msb
          LSR       A                ;put bottom bit into carry, 0 into top
          STA       Num_Blks         ;save as number of blocks to transfer
          BCC       CVTBLK           ;Carry is set from LSR to mark error.
$1        LDA       #XBYTECNT
          JMP       SysErr

                    ;Test for valid block number.Carry clear on return means no error.
                    ;Carry set means block number bad.  X register contains volume number.

CVTBLK    LDA       SosBuf
          STA       Buffer
          LDA       SosBuf+1
          STA       Buffer+1
          LDA       SosBuf+Extpg
          STA       Buffer+Extpg

          LDY       SosUnit
          LDA       SosBlk
          CMP       Block_LB,y
          LDA       SosBlk+1
          SBC       Block_HB,y
          BCC       $2

          LDA       #XBLKNUM
          JSR       SysErr

$2        LDA       SosBlk
          ADC       StBlk_LB,y
          STA       Sect_LB
          LDA       SosBlk+1
          ADC       StBlk_MB,y
          STA       Sect_MB
          LDA       StBlk_HB,y
          ADC       #000
          STA       Sect_HB
          RTS

                    ; SetupLBA - Programs devices task registers with LBA data
                    ; Input:
                    ;       partition data Sect_HB, MB, & LB
                    ;       X =  requested slot number in form $n0 where n =  slot 1 to 7
                    ; This function programs the device registers with
                    ; the ATA Logical Block Address (LBA) to be accessed.
                    ; A SOS block and a ATA sector are both 512 bytes.
                    ; Logical Block Mode, the Logical Block Address is interpreted as follows:
                    ; LBA07-LBA00: Sector Number Register D7-D0.
                    ; LBA15-LBA08: Cylinder Low Register D7-D0.
                    ; LBA23-LBA16: Cylinder High Register D7-D0.
                    ; LBA27-LBA24: Drive/Head Register bits HS3-HS0.

SetupLBA  JSR       CkDevice
          LDA       Sect_LB
          STA       ATSector,x       ;store low block # into LBA 0-7
          LDA       Sect_MB
          STA       ATSector+1,x     ;store mean block # into LBA 15-8
          LDA       Sect_HB
          STA       ATSector+2,x     ;store high block # LBA bits 23-16
          LDA       Num_Blks
          STA       ATSectCt,x       ;store # of blocks to be read/written
          LDY       #000
          TYA
          STA       ATdataHB,x
          ASL       Num_Blks         ;change blocks to number of pages
          ADC       Num_Blks+1       ;fix anomaly - If LB=$00 then don't increment HB
          STA       Num_Blks+1
          RTS

                    ;Bump is called to bump the buffer pointer by one page (256 bytes).
                    ;We increment the msb of the buffer pointer, and fall into FixUp to see if
                    ;we generated an anomaly(and fix it up).

BumpAdr   INC       Buffer+1         ;increment buffer msb

                    ;Fix up the buffer pointer to correct for an addressing anomalies!
                    ;Since we'll call Bump after each page, we just need to do the initial
                    ;checking for two cases:
                    ;00xx bank N- > 80xx bank N-1
                    ;20xx bank 8F if N was 0
                    ;ffxx bank N- > 7Fxx bank N+1

FixUp     LDA       Buffer+1         ;look at msb
          BEQ       $2               ;that's one!
          CMP       #0FF             ;is it the other one?
          BEQ       $3               ;yep. fix it!
          RTS       ;an easy one.
$2        LDA       #080             ;00xx -> 80xx
          STA       Buffer+1
          DEC       Buffer+Extpg     ;bank N -> band N-1
          LDA       Buffer+Extpg     ;see if it was bank 0
          CMP       #07F             ;(80) before the DEC.
          BNE       $4               ;nope! all fixed.
          LDA       #020             ;if it was, change both
          STA       Buffer+1         ;msb of address and
          LDA       #08F
          STA       Buffer+Extpg     ;bank number for bank 8F
          RTS
$3        CLC
          ROR       Buffer+1         ;FFxx ->7Fxx
          INC       Buffer+Extpg     ;bank N -> bank N+1
$4        RTS

                    ; D_READ call processing
DRead     JSR       CkCnt
          LDA       #ATACRead
          STA       ATA_Cmd
CRead     JSR       FixUp
          LDA       #000             ;Zero # bytes read
          STA       Count
          STA       Count+1
          TAY
          STA       (QtyRead),Y      ;bytes read
          INY
          STA       (QtyRead),Y      ;msb of bytes read
          LDA       Num_Blks         ;check for Num_Blks greater than zero
          ORA       Num_Blks+1
          BEQ       ReadDone         ;quantity to read is already zero
          JSR       Read_Blk         ;Transfer a block to/from the disk
          LDY       #000
          LDA       count
          STA       (QtyRead),y      ;Update # of bytes actually read from device
          INY
          LDA       count+1
          STA       (QtyRead),y
          BCS       IO_Error         ;An error occurred
ReadExit  RTS                        ;exit read routines - alls well that ends well

SRead     JSR       Read_Blk         ;Transfer a block to/from the disk
          BCC       ReadExit
IO_Error  LDA       #XIOERROR        ;I/O error
          JMP       SysErr

                    ;Read_Blk - Read requested blocks from device into memory
Read_Blk  JSR       SetupLBA         ;Program the device's task file registers
          LDA       ATA_Cmd
          STA       ATCmdReg,x       ;Issue the read,verify, identity command to the drive
$2        LDA       ATA_Stat,x       ;Wait for BUSY flag to clear
          BMI       $2
          AND       #009             ;Check for error response from device
          CMP       #001             ;If DRQ=0 and ERR=1 a device error occured
          BEQ       Save_Err

ReadIt    LDA       ATA_Stat,x
          BMI       ReadIt
          AND       #008             ;get DRQ status bit
          BEQ       Save_Err         ;if DRQ=0 then we didn't get enough data
          LDA       ATdataLB,x
          STA       (Buffer),y
          INY
          LDA       ATdataHB,x
          STA       (Buffer),y
          INY
          BNE       ReadIt
          JSR       BumpAdr
          INC       Count+1
          DEC       Num_Blks         ;did we get what was asked for
          BNE       ReadIt           ;No!  Go get next block.
          DEC       Num_Blks+1
          BPL       ReadIt
ReadDone  JMP       CSet2Mhz

Save_Err  LDA       ATAError,x
          STA       Err_Data
          LDA       ATSector,x       ;retrieve sector # error occurred
          STA       Err_Data+1       ;LB
          LDA       ATSector+1,x
          STA       Err_Data+2       ;MB
          LDA       ATSector+2,x
          STA       Err_Data+3       ;HB
          LDA       ATSectCt,x
          STA       Err_Data+4       ;retrieve # of sectors left
          STY       Count
          SEC
          JMP       Set2Mhz

                    ;D_WRITE call processing
DWrite    JSR       CkCnt
          LDA       #ATACWrit
          STA       ATA_Cmd
CWrite    JSR       FixUp
          LDA       Num_Blks         ;check for Num_Blks greater than zero
          ORA       Num_Blks+1
          BEQ       WritDone         ;quantity to write is zero

                    ; WriteBlk - Write requested blocks from memory to device
          JSR       SetupLBA         ;Program the device's task file registers
          LDA       ATA_Cmd
          STA       ATCmdReg,x       ;Issue the write/format command to the drive
$2        LDA       ATA_Stat,x       ;Wait for BUSY flag to clear
          BMI       $2
          AND       #009             ;Check for error response from device
          CMP       #001             ;If DRQ=0 and ERR=1 a device error occured
          BEQ       Writ_Err

WriteIt   LDA       ATA_Stat,x
          BMI       WriteIt
          AND       #008             ;get DRQ status bit
          BEQ       Writ_Err
          LDA       (Buffer),y
          PHA
          INY
          LDA       (Buffer),y
          STA       ATdataHB,x
          LDA       SetCSMsk,x       ;any access sets mask bit to block IDE -CS0 on
                                     ;I/O read to drive
          PLA
          STA       ATdataLB,x       ;Remember that all write cycles are
                                     ;preceded by a read cycle on the 6502
          LDA       ClrCSMsk,x       ;Set back to normal, allow CS0 assertions on
                                     ;read cycles
          INY
          BNE       WriteIt
          JSR       BumpAdr
          DEC       Num_Blks         ;did we do what was asked for
          BNE       WriteIt
          DEC       Num_Blks+1       ;we might have to do this one more time
          BPL       WriteIt
WritDone  JMP       CSet2Mhz         ;exit write routines - alls well that ends well.

Writ_Err  JSR       Save_Err
          JMP       IO_Error

                    ;D_STATUS call processing
                    ; $00   Physical Drive Number  ($00=master, $01=slave)
                    ; $01   Return device identification data - $0200 bytes long
                    ; $02   Return most recent device error information/data
                    ; $03   Return partition table DIB
                    ; $FE   Return preferrred bitmap location ($FFFF)

DStatus   LDA       CtlStat          ;status command
          BEQ       Status
          CMP       #001
          BEQ       S_Ident
          CMP       #002
          BEQ       ErrStat
          CMP       #003
          BEQ       ParTable
          CMP       #0FE
          BEQ       BitMap
CS_Bad    LDA       #XCTLCODE        ;control/status code no good
Err_Out3  JSR       SysErr

                    ;Return drive number & check device status
Status    LDY       #000
          LDA       CurDrive         ;Get DIB drive #
          STA       (CSList),Y
          JMP       CkDevice

                    ;Return partition table
ParTable  JSR       MFormat
          JSR       InitPmap
          LDY       #000
$1        LDA       PmapBuf,Y
          STA       (CSList),Y
          INY
          BNE       $1
          CLC
          RTS

                    ;Return most recent error data.
                    ; Byte 0:  Device Error Code
                    ; Byte 1,2,3:    Sector# (LB,MB,HB) error occurred
                    ; Byte 4: # of sectors left to transfer
ErrStat   LDY       #004
$1        LDA       Err_Data,y
          STA       (CSList),Y
          DEY
          BPL       $1
          CLC
          RTS

                    ;Return preferred bit map locations.  We return FFFF - don't care.
BitMap    LDY       #000
          LDA       #0FF
          STA       (CSList),Y
          INY
          STA       (CSList),Y       ;return FFFF, don't care
          CLC
          RTS       ;and leave

                    ; Device Identification
S_Ident   LDA       CSList
          STA       Buffer
          LDA       CSList+1
          STA       Buffer+1
          LDA       CSList+Extpg
          STA       Buffer+Extpg
          LDA       #ATAIdent
          STA       ATA_Cmd
C_Ident   LDA       #001
          STA       Num_Blks
          LDA       #000
          STA       Num_Blks+1
          JMP       SRead

                    ;D_CONTROL call processing
                    ; $00     Reset device
                    ; $01     Perform device I/O function with user supplied call block
                    ; $FE     Perform media formatting

DControl  LDA       CtlStat          ;control command
          BEQ       ResetDev
          CMP       #0FE             ;formatting?
          BEQ       MFormat
          CMP       #001
          BEQ       UserIO
          JMP       CS_Bad           ;Control code no good!

                    ;Execute reset call to ATA device
ResetDev  JSR       Set1Mhz
          LDX       SlotCX
          LDA       ClrCSMsk,x       ;reset MASK bit in PLD for normal CS0 signaling
          LDA       #000
          STA       ATdataHB,x       ;Clear high byte data latch
          LDA       #006             ;Reset bit=1, Disable INTRQ=1
          STA       ATAdCtrl,x
                    ; Per ATA-6 spec, need to wait 5us minimum. Use a delay of 29us.
          LDA       #Wait29us
          JSR       Wait
          LDA       #002             ;Reset bit=0, Disable INTRQ=1
          STA       ATAdCtrl,x
                         ; Per ATA-6 spec, need to wait 2ms minimum. Use a delay of 10ms.
          LDA       #Wait10ms
          JSR       Wait
                         ; Per ATA-6 spec, wait for busy to clear
$1        LDA       ATA_Stat,x
          BMI       $1
          JMP       CSet2Mhz

                    ;Execute media formatting call.
MFormat   LDX       CurDrive
          LDA       #0FF             ;Invalidate partition table status
          STA       PartStat,X       ;so subsequent read/writes
          CLC                        ;will re-initialize the partition
          RTS                        ;table before writing directory.

CtrlCmds  .BYTE     ATA_Xerr,ATACRead,ATACWrit,ATA_Vrfy,ATA_Frmt,ATA_Diag,ATAIdent

Cmd_Tbl   .WORD     Send_Cmd-1       ;Extended Error Info  $03
          .WORD     CRead-1          ;Sector Read  $20
          .WORD     CWrite-1         ;Sector Write  $30
          .WORD     Verify-1         ;Read verify  $40
          .WORD     CWrite-1         ;Sector Format  $50
          .WORD     Send_Cmd-1       ;Internal Diagnostic Test  $90
          .WORD     C_Ident-1        ;Device Identity  $EC

                    ;Perform I/O with user supplied call block
                    ;Call Block Organization:
                    ;    Byte 0:        ATA Command Code
                    ;    Byte 1,2,3:    Sector# (HB,MB,LB)
                    ;    Byte 4:        # of sectors
                    ;    Byte 5-6       Bytes returned to buffer
                    ;    Byte 7...      Data Buffer

UserIO    LDY       #004
$1        LDA       (CSList),Y
          STA       ATA_Cmd+1,Y
          DEY
          BNE       $1
          LDA       Num_Blks         ;if zero then 256 blocks is requested
          BNE       $2
          INY
$2        STY       Num_Blks+1

          CLC                        ;Setup data addresses
          LDA       CSList
          ADC       #005
          STA       QtyRead
          LDA       CSList+1
          ADC       #000
          STA       QtyRead+1
          LDA       QtyRead
          ADC       #002
          STA       Buffer
          LDA       QtyRead+1
          ADC       #000
          STA       Buffer+1
          LDA       CSList+Extpg
          STA       QtyRead+Extpg
          STA       Buffer+Extpg
          LDY       #000
          LDA       (CSList),Y
          LDY       #006
$3        CMP       Ctrl_Cmds,y
          BEQ       $4
          DEY
          BPL       $3
          JMP       CS_Bad
$4        STA       ATA_Cmd
          TYA
          ASL       a
          TAY
          LDA       Cmd_Tbl+1,Y
          PHA
          LDA       Cmd_Tbl,Y
          PHA
          RTS

                    ;Device Internal Diagnostic Routine  ATA Command $90
                    ; Returns 1 byte of diagnostic code in Buffer
                    ; $01 = No Error Detected
                    ; $02 = Formatter Device Error
                    ; $03 = Sector Buffer Error
                    ; $04 = ECC Circuitry Error
                    ; $05 = Controlling Microprocessor Error
                    ; $8x = Slave Failed (true IDE mode)

                    ;Extended Error Code Request
                    ; Returns 1 byte of exteded error code in Buffer
                    ; $00 = No Error Detected
                    ; $01 = Self test OK (No error)
                    ; $09 = Miscellaneous Error
                    ; $20 = Invalid Command
                    ; $21 = Invalid address (requested head or sector invalid)
                    ; $2F = Address Overflow (address too large)
                    ; $35,$36 = Supply voltage out of tolerance
                    ; $11 = Uncorrectable ECC error
                    ; $18 = Corrected ECC Error
                    ; $05,$30-34,$37,$3E = Self test or diagnostic failed
                    ; $10,$14 = ID not found
                    ; $3A = Spare sectors exhausted
                    ; $1F = Data transfer error/Aborted command
                    ; $0C,$38,$3B,$3C,$3F = Corrupted Media Format
                    ; $03 = Write/Erase failed

Send_Cmd  JSR       CkDevice
          LDA       #000
          STA       ATdataHB,x       ;Clear high byte data latch
          LDA       ATA_Cmd
          STA       ATCmdReg,x       ;Issue the ATA command to the drive
$1        LDA       ATA_Stat,x
          BMI       $1
          LDA       ATAError,x
          LDY       #000
          STA       (Buffer),y
          JMP       CSet2Mhz

                    ;Verify - Verify requested blocks
Verify    JSR       SetupLBA         ;Program the device's task file registers
          LDA       #ATA_Vrfy
          STA       ATCmdReg,x       ;Issue the read verify command to the drive
$1        LDA       ATA_Stat,x       ;Wait for BUSY flag to clear
          BMI       $1
          LSR       a
          BCS       $2
          JMP       Set2Mhz
$2        JMP       Writ_Err

                    ;Supplemental Subroutines
                    ; Checks to see if the drive status register is readable and equal to $50
                    ; Waits up to 10 seconds for drive to become ready
                    ; If so, return with the carry clear, otherwise bailout
                    ; with a DRIVE NOT FOUND error.
CkDevice  JSR       Set1Mhz
          LDX       SlotCX
          LDA       ClrCSMsk,x       ;reset MASK bit in PLD for normal CS0 signaling
$1        LDA       ATA_Stat,x
          BMI       $1
          LDA       Drv_Parm         ;Select drive to check
          STA       ATAHead,x
          LDY       #000
$2        LDA       ATA_Stat,x
          BMI       $2
          AND       #058             ;%01011000
          CMP       #050             ;if RDY=1, DSC=1, and DRQ=0
          BNE       $3
          CLC
          RTS
$3        LDA       #Wait100m
          JSR       Wait             ;Wait 100ms for device to be ready
          INY
          CPY       #100.            ;Wait up to 10 seconds for drive to be ready
          BCC       $2
          JSR       Set2Mhz
          LDA       #XNODRIVE
          JMP       SysErr           ;Never returns

                    ; Wait - Copy of Apple's wait routine.
                    ; Input:
                    ;       A =  desired delay time, where Delay(us) =  .5(5A^2 + 27A + 26)
                    ;       or more usefully: A =  (Delay[in uS]/2.5 + 2.09)^.5 - 2.7
Wait      SEC
$1        PHA
$2        SBC       #001
          BNE       $2
          PLA
          SBC       #001
          BNE       $1
          RTS

                    ; Throttle back to 1 Mhz
Set1Mhz   PHP
          SEI
          LDA       EReg
          ORA       #080
          STA       EReg
          PLP
          RTS

                    ; Throttle up to 2 Mhz
CSet2Mhz  CLC
Set2Mhz   PHP
          SEI
          LDA       EReg
          AND       #07F
          STA       EReg
          PLP
          RTS

