Thanks for the explanation and the code... that's pretty short and kinda hard-coded, so that does give you quite a short loop for reading sectors in. By comparison, my code path is quite a bit more involved, but there's a reason for it... as these are all BIOS routines which are flexible and perform error checking and save the final status in a Page Zero location.
I've used several Page Zero locations to hold parameters for LBA number, Transfer Address and there are two locations used to hold the working address via indirect addressing, i.e., LDA (XFER_ADDR). Here's a few code snippets that are being used for setting up and doing the transfer of data via interrupt:
Code:
IDE_READ_LBA ;Read a Block of data from IDE device
;
; This routine requires loading the requested LBA into the appropriate registers and
; issuing the READ command 20h. The LBA count supported for the BIOS are bits 0-23,
; so bits 24-27 are always set to 0. This provides access to IDE devices up to 8GB.
;
; Once the registers/parameters are setup, the Read Block command is issued.
; This results in an interrupt being generated. The ISR handles the transfer of LBA
; data from the CF Card to memory.
;
; The registers used are the same for read/write/verify. These are:
;
; IDE_COMMAND = function requested (20h = READ LBA command)
; IDE_DRV_HEAD = (Upper 4 bits) used as:
; bit 7 = 1 per Sandisk documentation
; bit 6 = 1 for LBA mode
; bit 5 = 1 per Sandisk documentation
; bit 4 = 0 for Drive 0
; IDE_DRV_HEAD = LBA Address bits 27-24 (lower 4 bits) - not used, always 0000
; IDE_CYL_HIGH = LBA Address bits 23-16
; IDE_CYL_LOW = LBA Address bits 15-8
; IDE_SCT_NUM = LBA Address bits 7-0
; IDE_SCT_CNT = number of blocks to read (most CF-Cards are limited to 1)
;
JSR IDE_SET_PARMS ;Setup required parameters (6)
;
SMB3 MATCH ;Set Read LBA bit (5)
STZ IDE_STATUS_RAM ;Clear RAM Status Register, ISR updates it (3)
LDA #$20 ;Get Read LBA command (2)
IDENT_READ STA IDE_COMMAND ;Send command to IDE Controller (4)
LBA_RD_CMD
LDA IDE_ALT_STATUS ;Get IDE Alternate Status register (4)
BPL LBA_RD_CMD ;Loop until IDE controller Busy (2/3)
;
LBA_RD_WAIT
LDA IDE_ALT_STATUS ;Get IDE Alternate Status register (4)
CMP #$50 ;Compare for ready (2)
BEQ LBA_RD_NORM ;If normal status, finish up and exit (2/3)
CMP #$51 ;Compare for error bit set (2)
BEQ LBA_RD_ERR ;Branch to handle error (2/3)
BRA LBA_RD_WAIT ;Else branch back (waiting on IDE controller) (3)
;
LBA_RD_ERR
RMB3 MATCH ;Reset Read LBA bit (no ISR invoked) (5)
LDA IDE_ALT_STATUS ;Get IDE Alternate Status register (4)
STA IDE_STATUS_RAM ;Update RAM Status Register (3)
RTS ;Return to caller (7)
;
LBA_RD_NORM
BBS2 MATCH,LBA_RD_NORM ;Wait for Read completed via ISR (5)
RTS ;Return to caller (status in A Reg) (7)
;
Code:
; This routine sets the LBA number used for all transfers.
; - The IDE Controller is checked first to ensure it's ready to receive parameters
; - then the requested LBA (stored in Page Zero variables) are loaded into the
; - IDE Controller registers, followed by the required Mode parameters.
; - Last, the transfer address is setup which points to the location in memory that
; - will be used to transfer Data to or from.
;
IDE_SET_PARMS ;Set All parameters for LBA transfers
;
LDA IDE_ALT_STATUS ;Get IDE Alternate Status register (4)
BMI IDE_SET_PARMS ;Loop until it's clear (2/3)
;
LDA LBA_EXT_BYTE ;Set LBA bits 23-16 (3)
STA IDE_CYL_HIGH ;Send to IDE (4)
LDA LBA_HIGH_BYTE ;Set LBA bits 15-8 (3)
STA IDE_CYL_LOW ;Send to IDE (4)
LDA LBA_LOW_BYTE ;Get LBA bits 7-0 (3)
STA IDE_SCT_NUM ;Send to IDE (4)
LDA LBA_XFER_CNT ;Get Block count to read (CF always 1) (3)
STA IDE_SCT_CNT ;Send to IDE (4)
;
IDE_SET_PARMS2 ;Set partial parameters (non LBA xfer commands)
;
LDA #%11100000 ;Set Drive 0, LBA mode, LBA bits 27-24 as 0 (2)
STA IDE_DRV_HEAD ;Send to IDE (4)
;
LDA LBA_ADDR_LOW ;Setup buffer address (3)
STA BIOS_XFERL ;Store low byte (3)
LDA LBA_ADDR_HIGH ;Block Buffer Address (3)
STA BIOS_XFERH ;Store high byte (3)
RTS ;Return to caller (7)
Code:
;
; NOTE: 25 clock cycles to here if UART ISR is second!
; NOTE: 40 clock cycles to here if NO UART interrupt occurs!
;
INTERUPT1 ;Interrupt 1
LDA IDE_ALT_STATUS ;Get Alternate Status Register (4)
BMI REGEXT01 ;If Busy bit active, just exit (2/3)
;
; - Check for Data Request (DRQ), as it's the only function that will require servicing
; against the IDE controller.
;
LDA IDE_STATUS ;Get Status (resets IRQ) (4)
AND #%00001000 ;Check for DRQ (2)
BNE IDE_READ_BLK ;Branch if active (2/3)
;
; - If no DRQ, possible Write or Verify Block command, check for these next.
;
BBS2 MATCH,IDE_WRIT_BLK ;If Bit 2 set, Write operation (5)
BBS1 MATCH,IDE_VRFY_BLK ;If Bit 1 set, Verify operation (5)
BRA REGEXT01 ;No IDE interrupt, exit the ISR handler (3)
;
IDE_READ_BLK ;IDE Read a Block of data
BBR3 MATCH,REGEXT01 ;If Bit 3 not set, exit ISR (rogue IRQ?) (5)
;
LBA_XFER LDA IDE_ALT_STATUS ;Get Status (4)
AND #%00001000 ;Check for DRQ (2)
BEQ IDE_RD_DONE ;If not active, done, exit (2/3)
;
IDE_RD_RBLK
LDA IDE_DATA ;Read low byte (high byte in latch) (4)
STA (BIOS_XFERL) ;Store low byte (5)
INC BIOS_XFERL ;Increment pointers (5)
BNE IDE_RD_BLK1 ; (2/3)
INC BIOS_XFERH ; (5)
IDE_RD_BLK1
LDA IDE_16_READ ;Read high byte from latch (4)
STA (BIOS_XFERL) ;Store high byte (5)
INC BIOS_XFERL ;Increment pointers (5)
BNE LBA_XFER ;Loop back to Xfer, saves 3 clock cycles (2/3)
INC BIOS_XFERH ; (5)
IDE_RD_BLK2
BRA LBA_XFER ;Loop back till no more DRQs (3)
;
IDE_RD_DONE RMB3 MATCH ;Clear Read Block flag (5)
;
IDE_ALL_DONE LDA IDE_ALT_STATUS ;Get IDE status (4)
STA IDE_STATUS_RAM ;Save it to RAM location (3)
REGEXT01 JMP (VECINSRT0) ;Exit ISR handler (6)
;
IDE_WRIT_BLK ;IDE Write a Block of data
RMB2 MATCH ;Clear Write Block flag (5)
BRA IDE_ALL_DONE ;Branch and finish ISR (3)
;
IDE_VRFY_BLK ;IDE Verify a Block of data
RMB1 MATCH ;Clear Verify Block flag (5)
BRA IDE_ALL_DONE ;Branch and finish ISR (3)
Code:
;
IRQ_VECTOR ;This is the ROM start for the BRK/IRQ handler
PHA ;Save A Reg (3)
PHX ;Save X Reg (3)
PHY ;Save Y Reg (3)
TSX ;Get Stack pointer (2)
LDA $0100+4,X ;Get Status Register (4)
AND #$10 ;Mask for BRK bit set (2)
BNE DO_BRK ;If set, handle BRK (2/3)
JMP (IRQVEC0) ;Jump to Soft vectored IRQ Handler (6)
DO_BRK JMP (BRKVEC0) ;Jump to Soft vectored BRK Handler (6)
NMI_ROM JMP (NMIVEC0) ;Jump to Soft vectored NMI handler (6)
;
;This is the standard return for the IRQ/BRK handler routines
;
IRQ_EXIT0 PLY ;Restore Y Reg (4)
PLX ;Restore X Reg (4)
PLA ;Restore A Reg (4)
RTI ;Return from IRQ/BRK routine (6)
;
As you can see, a fair amount of code... but additional interrupt handlers can be added to pre or post mode, and the BRK instruction is also checked for when the IRQ vector is called.... along with registers saved and restored. You'll also notice many instructions have a number in parentheses at the end of the comment, which is the number of clock cycles to execute each one. There's also a JUMP table in ROM that points to many routines, so you have a common address to call them from, regardless of future code changes. Finally, here's the shorter routine that is used to benchmark the 16MB sequential read:
Code:
; Simple test program to transfer multiple sectors - READ
;
LDA #$00 ;Get transfer address
LDY #$10 ; of $1000
LDX #$01 ;Sector count of 1
JSR $FF15 ;Call BIOS routine to set it
LDA #$00 ;Get LBA starting block
LDY #$00 ; of $000000
LDX #$00 ;
JSR $FF12 ;Call BIOS to set it
;
LDX #$00 ;Set for 256 blocks (128KB)
LDY #$80 ;Set multiplier of 128 (* 256)
JSR B_CNT_STRT ;Reset and start benchmark counter
;
LBA_RBLK
JSR $FF09 ;Call BIOS Read Block
LDA IDE_STATUS_RAM ;Get IDE Status (RAM)
LSR A ;Shift error bit into carry
BCS RD_ERR ;Branch if error
INC $FA ;Else, increment LBA low number
BNE SKP_HI_RD ;Skip if no rollover
INC $FB ;Else, increment LBA high byte
;
SKP_HI_RD
DEX ;Decrement low index
BNE LBA_RBLK ;Loop back until zero
DEY ;Decrement multiplier index
BNE LBA_RBLK ;Loop back until done
JSR M_CROUT ;Send CR/LF
JSR M_QUITB ;Quit Benchmark counter, print results
RTS ;Return to caller
;
RD_ERR
JSR B_IDE_GET_STAT ;Get Extended error code (X reg)
BRK ;Enter Monitor, display registers
;