;Low Level Disc Operations
;-------------------------
;Handles read/write/verify disc operations
;
;Entry:
; R1 = bits 0 - 3 reason code
;           4 - 7 option bits
; R2 = disc address, top 3 bits drive
; R3 = pointer to buffer
; R4 = length in bytes
; R5 = pointer to alternative disc record
; R6 = pointer to defect list?
; R8 = pointer to FileCore instance private word
;
;Important registers:
; R9  = size of sector log2
; R10 = R2 on entry, is incremented on each read/write and passed back
;       as R2 on exit
; R11 = R1 on entry

.ADFS_DiscOp	DCD 0			;address of ADFS DiscOp entry

.Entry_DiscOp
 STMFD   R13!, {R0, R8}
 #if compile_RO == 311
   MOV     R8, PC
 #else
   MRS     R8, CPSR
 #endif
 TST     R2, #&E0000000			;is it drive 0?
 BNE     Pass_DiscOp_to_ADFS		;NO, forward request to ADFS
 TEMP    R0
 STR     R8, DiscOp_CPSR		;store CPSR for exit
 LOCK    R0

 LDR     R0, recording_addr
 TEQ     R0, #0				;are we recording?
 BNE     capture_DiscOp_read		;capture this DiscOp
 .capture_DiscOp_read_return
 LDR     R0, ADF_Mounted
 TEQ     R0, #0				;do we have a floppy mounted
 BNE     Capture_DiscOp			;YES, we need to handle the request
					;fall through with R0=0
 .Pass_DiscOp_to_ADFS
 #if compile_RO == 311
   TEQP    R8, #0
 #else
   MSR     CPSR_f, R8
 #endif
 LDMFD   R13!, {R0, R8}
;@@@ commented lines are to capture return of ADFS_DiscOp
;STR     R14, [R13, #-4]!
;ADR     R14, _capture_return
 LDR     PC, ADFS_DiscOp		;pass to ADFS
;._capture_return
;LDR     R14, [R13], #4
;@@@

 .Capture_DiscOp
 LDMFD   R13!, {R0, R8}			;clear stack
 STMFD   R13!, {R1, R9-R12, R14}

 BL      Prevent_ScatterList_Adds	;prevent scatterlist additions

 ;------------------------------------- Move head based on time
 LDR     R0, ADF_Mounted
 TEQ     R0, #1				;ADF mounted?
 LDRNE   R11, head_track                ;NO,
 CMNNE   R11, #1			; |+ is the head parked?
 BEQ     Entry_DiscOp_head_parked	;YES, skip head moving code

 AND     R1, R1, #%1111
 CMP     R1, #3				;are we performing a read/write sector
 BHS     Entry_DiscOp_head_parked	;NO, skip head code

 SWI     XOS_ReadMonotonicTime		;PRM1-446
 ADR     R9, time_last
 LDR     R1, [R9]
 STR     R0, [R9]			;update time

 SUBS    R9, R0, R1			;R9 = time in centiseconds
 BMI     Entry_DiscOp_head_parked	;skip head code if negative

 LDR     R12, head_position		;current head position
 LDR     R1, [R12, #0]
 CMN     R1, #1				;end of track?
 LDRNEB  R1, [R12, #O_time]		;time offset of current sector
 TEQ     R1, #&FF			;does it have a time?
 BEQ     Entry_DiscOp_head_parked	;NO, skip moving
 ADDNE   R9, R9, R1			;NO, advance by current head time
 MOV     R1, #Track_Length_MS
 TEMP    R14
 DIV     R0, R9, R1			;R9 = time MOD track length in ms
 LOCK    R14

 LDRB    R14, [R5, #DR_density]		;density we're looking for
 ._L1					;now need to sector with time>R9
   LDRB    R0, [R11, #O_time]		;sector time
   TEQ     R0, #&FF			;are we at end of track?
   BEQ     _L1_exit			;YES, exit

   CMP     R0, R9			;is this sector the one we want?
   LDRHSB  R0, [R11, #O_density]	;YES, load the density
   ANDHS   R0, R0, #bits_density
   TEQHS   R0, R14			;is it the one we're looking for?

   ADDNE   R11, R11, #2*4		;NO, go to the next sector
 BNE     _L1

 ._L1_exit
 TEMP    R1
 STR     R11, head_position		;store the new head position
 LOCK    R1
 ;-------------------------------------

 .Entry_DiscOp_head_parked
 LDR     R1, [R13]			;get R1 from stack, as its now corrupt

 MOV     R10, #0
 TEMP    R9
 STR     R10, DiscOp_return_error	;clear return error
 LOCK    R9

 LDRB    R9, [R5, #DR_log2secsize]	;R9 = size of sector log2
 LDR     R0, ADFS_FS_Create_Block	;is ADFS running in sector mode?
 TST     R0, #ADFS_Sector_Aligned
 MOVEQ   R10, R2			;NO, R10 will track original disc address
 MOVNE   R10, R2, LSL R9		;YES, change to byte offset
 MOV     R11, R1			;preserve R1 in R11 for later use

 AND     R0, R1, #%1111			;what are we being called for?

 CMP     R0, #2
 BEQ     DiscOp_Write			;2 = write
 BLO     DiscOp_Read			;0 = verify / 1 = read

 CMP     R0, #4
 BLO     DiscOp_ReadTrack		;3 = read track

.Entry_DiscOp_Exit			;exit for all others restoring flags
 LDR     R1, DiscOp_CPSR
 BIC     R1, R1, #1 << 28		;clear V
 #if compile_RO > 311
   MSR     CPSR_f, R1
 #endif
 #if compile_RO == 311
   TEQP    R1, #0
 #endif
LDMFD   R13!, {R1, R9-R12, PC}




;Convert_Sector_to_Addr
;----------------------
;Converts a sector offset into a byte offset
;based on current disc sig
;
;Entry:
; R5  - pointer to discrec
; R9  - log2secsize
; R10 - disc address
;
;Exit:
; R2  - size of sector
; R14 - memory address of data
; V   - set on unrecoverable error

.Convert_Sector_to_Addr
 STMFD   R13!, {R0, R3, R11-R12, R14}

 LDR     R11, ADF_Mounted
 TEQ     R11, #1			;ADF?
 BEQ     Convert_Sector_to_Addr_ADF	;YES

;@@@@@@ fix, needs to convert disc size before checking
; LDR     R12, [R5, #DR_discsize]	;get disc size from disc rec
; CMP     R10, R12			;is the disc address within the disc
; MOVHS   R0, #Error_Bad_Parameters	;NO?
; BHS     Convert_Sector_to_Addr_exit	;|+ exit with error

 MOV     R0, R10, LSR R9		;R0 = disc address in sectors
 LDRB    R12, [R5, #DR_secspertrack]	;R12 = sectors per track
 M_DIV   R0, R12, R11			;R0 (Track) = R0/R12  R11=remainder (Sector)

 LDR     R12, ADF_Mounted
 TEQ     R12, #2			;APD mounted?
 BNE     _P1				;NO
 LDRB    R12, [R5, #DR_lowsector]	;YES,
 TST     R12, #%01000000		; |  is the disc sequential?
 MOVNE   R0, R0, LSL #1			; |+ YES, double track to compensate
 ._P1

 CMP     R0, #Max_Track_Count		;check the track is beyond our max
 MOVHS   R0, #Error_Bad_Parameters	;YES
 BHS     Convert_Sector_to_Addr_exit	;|+ exit with error

 LDR     R12, APD_Tracks		;track info pointer
 LDR     R12, [R12, R0, LSL #2]		;R12 = offset in sector info table

 CMN     R12, #1			;is the track pointer -1
 LDREQ   R0, FDC_Error_Sector_Not_Found	;YES?
 BEQ     Convert_Sector_to_Addr_exit	;...exit with error, unformatted

					;now, find the sector
 LDR     R0, APD_Sectors		;sector info pointer
 ADD     R12, R0, R12			;R12 = address of sector table
 MOV     R3, R12			;store beginning of track for head positioning
 LDR     R0, head_track			;now position head
 TEQ     R12, R0			;...if we're on the same track
 LDREQ   R12, head_position		;...position, the head
 TEMP    R14
 STRNE   R12, head_track		;otherwise begin at start of track
 STRNE   R12, head_position
 LOCK    R14
 MOV     R2, R12			;R2=original head position
 LDRB    R14, [R5, #DR_density]		;density we're looking for
 ._L1
   LDR     R0, [R12]			;load the sector ID
   CMN     R0, #1			;is it -1
   MOVEQ   R12, R3			;YES, loop to start of track
   TEQEQ   R12, R2			;processed whole track?
   LDREQ   R0, FDC_Error_Sector_Not_Found	;YES it was
   BEQ     Convert_Sector_to_Addr_exit	;...exit with error, no sectors left

   LDRB    R0, [R12, #O_sector_no]	;load the sector no
   TEQ     R0, R11			;is it the one we're looking for?
   LDREQB  R0, [R12, #O_density]	;load the density
   ANDEQ   R0, R0, #bits_density	;mask the density bits
   TEQEQ   R0, R14			;is it the one we're looking for?
   BEQ     _L1_exit			;YES, exit
   ADD     R12, R12, #8			;go to the next sector

   TEQ     R12, R2			;have we stepped through the whole track?
   LDREQ   R0, FDC_Error_Sector_Not_Found	;YES
   BEQ     Convert_Sector_to_Addr_exit	;...exit with error, sector wasn't found
 B       _L1

 ._L1_exit
 ADD     R2, R12, #8			;advance the disc head
 TEMP    R11
 STR     R2, head_position
 LOCK    R11
 LDR     R11, [R12]			;load the sector ID
 AND     R14, R11, #bits_sector_length	;restrict sector length
 ADD     R14, R14, #7			;and convert back to 128 / 256 / 512 / 1024
 MOV     R2, #1
 MOV     R2, R2, LSL R14		;R2 = sector size in bytes

 MOV     R0, #0				;assume no error
 TST     R11, #bit_ID_CRC		;ID CRC okay?
 MOVNE   R0, #Error_ID_CRC
 TST     R11, #bit_sector_CRC		;Data CRC okay?
 LDRNE   R0, FDC_Error_Sector_CRC
 TST     R11, #bit_slow_sector		;is this a slow sector?
 BLNE    read_slow_sector		;YES, pause

 LDR     R14, [R12, #4]			;load the disc byte offset of requested sector

 .Convert_Sector_to_Addr_exit
 LDR     R3, ADF_Buffer_Address		;source
 ADD     R14, R14, R3			;return the address of the requested sector

 ADDS    R3, R0, R0			;clear V and test if R0 is zero
 LDMEQFD R13!, {R0, R3, R11-R12, PC}	;no error, exit

 TEMP    R3
 STR     R0, DiscOp_return_error	;store error
 LOCK    R3
 LDR     R3, FDC_Error_Sector_CRC
 TEQ     R0, R3				;is it recoverable
 MOVNE   R3, #1<<31			;NO
 ADDNES  R3, R3, R3			;..set V
LDMFD   R13!, {R0, R3, R11-R12, PC}


.Convert_Sector_to_Addr_ADF
 MOV     R2, #1				;work out sector size
 MOV     R2, R2, LSL R9			;...in bytes

 LDR     R3, ADF_Buffer_Length
 ADD     R11, R10, R2
 CMP     R11, R3			;is the sector outside the disk?
 LDRHI   R0, FDC_Error_Sector_Not_Found	;YES, exit with error
 MOVLS   R0, #0				;NO
 MOV     R14, R10
B       Convert_Sector_to_Addr_exit




;read_slow_sector
;---------------
;pause as if reading from an actual floppy, 200cs / track
;
;Entry:
; R12 - pointer to sector ID

.read_slow_sector
 STMFD   R13!, {R0-R3, R14}

 MOV     R3, #1				;R3=retry count + 1
 LDR     R0, [R12]			;are there any errors?
 ANDS    R0, R0, #bit_ID_CRC + bit_sector_CRC
 BEQ     _no_error

 MOV     R0, #0				;get number of retries
 MOV     R1, #0
 SWI     XADFS_Retries			;PRM2-288

 MOV     R3, R2, LSR #8
 AND     R3, R3, #&FF			;R3=retry count
 ADD     R3, R3, #1			;need to add one for initial delay

 ._no_error
 LDRB    R1, [R12, #O_sector_length]	;load the sector length
 ADD     R1, R1, #7			;sector length = 128 / 256 / 512 / 1024
 MOV     R2, #1
 MOV     R2, R2, LSL R1			;R2 = sector size in bytes

 LDRB    R0, [R12, #O_density]		;calculate time delay based on density

 TEQ     R0, #1				;SD
 MOVEQ   R1, #160			;|  125kbit/sec SD (160 bytes / cs)
 ADDEQ   R2, R2, #62+57			;|+ add header overhead bytes PRM2-270
 TEQ     R0, #2				;DD
 MOVEQ   R1, #320			;|  250kbit/sec DD (320 bytes / cs)
 ADDEQ   R2, R2, #62+90			;|+ add header overhead bytes PRM2-270
 TEQ     R0, #4				;QD
 MOVEQ   R1, #640			;|  500kbit/sec DD (640 bytes / cs)
 ADDEQ   R2, R2, #62+90			;|+ add header overhead bytes PRM2-271

 MOV     R14, #14			;seek time in cs
 MLA     R0, R1, R14, R2		;R0 = effective bytes we're reading
 MUL     R2, R0, R3			;..then multiply by the retry count

 M_DIV   R2, R1, R0			;calculate cs, R2 = R2 / R1
					;ie bytes to read/bytes per cs
					;ensure IRQ are enabled
 #if compile_RO == 311
   MOV     R1, PC
   BIC     R3, R1, #%11 << 26		;enable IRQ's
   TEQP    R3, #0
 #else
   MRS     R1, CPSR
   BIC     R3, R1, #%11 << 6
   MSR     CPSR_all, R3
 #endif
 SWI     XOS_ReadMonotonicTime		;read the current time
 ADD     R2, R0, R2			;R2 is time to wait until
 ._L1
   SWI     XOS_ReadMonotonicTime
   CMP     R0, R2
 BLO     _L1
 #if compile_RO == 311
   TEQP    R1, #0
 #else
   MSR     CPSR_all, R1			;restore flags
 #endif

LDMFD   R13!, {R0-R3, PC}




;DiscOp 1
;--------
;Entry:
; R3 = pointer to buffer
; R4 = length in bytes
; R5 = pointer to disc record
; R9 = log2secsize
;R10 = disc address in bytes
;R11 = R1 as passed by FileCore

;Exit:
; R4  - bytes remaining on error, zero if successful
; R10 - updated

.DiscOp_Read
 TST     R11, #ScatterList_Op
 BNE     _Read_ScatterList		;deal with scatterlist

 TEQ     R4, #0				;if no bytes requested, exit immediately
 BEQ     Entry_DiscOp_Exit

 ADR     R14, non_scatter_original
 STMIA   R14, {R3, R4, R10}		;if we get a CRC error, exit with these
 ADR     R14, DiscOp_local_scatter
 STMIA   R14, {R3, R4}			;use scatterlist to perform read
 ADD     R3, R3, R4			;set R3 to final address
 TEMP    R1
 STR     R3, non_scatter_R3_return	;..and save so we can restore on exit from
 LOCK    R1
 MOV     R3, R14			;..the scatterlist code

;fall through to DiscOp_Read_ScatterList

;Entry:
; R11 - R1 as passed by FileCore

 ._Read_ScatterList
   LDMIA   R3, {R1, R12}		;read the address and length

   CMN     R1, #Scatter_List_End	;if <= -65536 the scatter list looped
#if compile_RO == 311
   ADDCS   R3, R3, R1			;...go back to the start of the list
   LDMCSIA R3, {R1, R12}		;...and load the next pair
#else
   TEQCS   R12, #0			;...and R12=0
   ADDEQ   R3, R3, R1			;...go back to the start of the list
   LDMEQIA R3, {R1, R12}		;...and load the next pair
#endif
   TEQ     R12, #0			;are we at the end of the list?
   BEQ     DiscOp_ScatterList_exit

   BL      Convert_Sector_to_Addr	;convert it to our internal address
   BVS     DiscOp_ScatterList_exit	;exit on critical error
   MOV     R0, R14

   CMP     R12, R2			;is this scatter entry more than one sector?
   MOVLS   R2, R12			;NO, we can read in one go

   TST     R11, #Background_Op		;are we running a background transfer?
   BNE     _P1				;YES, skip foreground limit check
     CMP     R2, R4
     MOVHI   R2, R4			;cap transfer to foreground limit
   ._P1

   ANDS    R14, R11, #%1111		;are we performing a Verify?
   ADDEQ   R1, R1, R2			;YES, increase the address
   BLNE    MemCopy			;NO
   ADD     R10, R10, R2			;next disc address
					;now update the address / bytes remaining
   SUBS    R12, R12, R2			;reduce remainder
   STMNEIA R3, {R1, R12}		;NO, we need to stay on it then
   STMEQIA R3!, {R1, R12}		;YES, go to next

   SUBS    R4, R4, R2			;reduce foreground bytes remaining
   TSTEQ   R11, #Background_Op		;are we performing a background transfer?
 BNE     _Read_ScatterList		;loop while bytes still remaining


;Entry
; R3  - scatter list pointer
; R4  - bytes remaining, if any
; R10 - final disc address
; R11 - R1 as passed by FileCore

.DiscOp_ScatterList_exit
 LDR     R0, ADFS_FS_Create_Block	;do we need to release an FIQ?
 TST     R0, #Floppy_FIQ
 LDRNE   R12, ADFS_FileCore_Private	;YES
 ADRNE   R0, FileCore_Release_FIQ	;|
 MOVNE   R14, PC			;|
 LDRNE   PC, [R0]			;|+ notify FileCore to release

 TST     R11, #ScatterList_Op		;was this a scatter list?
 BNE     DiscOp_ReadWrite_Exit		;YES, don't check R3 return value
 LDR     R0, DiscOp_return_error	;error to return, if any
 LDR     R14, FDC_Error_Sector_CRC
 TEQ     R0, R14			;CRC error?
 TEQNE   R0, #Error_ID_CRC		;ID CRC error?
 ADREQ   R14, non_scatter_original
 LDMEQIA R14, {R3, R4, R10}		;YES, restore R3, R4 to the original values
 BEQ     DiscOp_ReadWrite_Exit
 LDR     R3, non_scatter_R3_return	;NO,  restore R3 to the updated address

 TST     R11, #Background_Op		;background discop?
 SUBEQ   R3, R3, R4			;NO, correct address in case R4 isn't zero

.DiscOp_ReadWrite_Exit
 LDR     R0, ADFS_FS_Create_Block
 TST     R0, #ADFS_Sector_Aligned	;is ADFS running in sector mode?
 MOVEQ   R2, R10			;NO, leave as bytes
 MOVNE   R2, R10, LSR R9		;YES, convert back to sectors

 LDR     R0, DiscOp_return_error	;error to return, if any
 TEQ     R0, #0				;NO, other error?
 BNE     DiscOp_Error_Exit		;...exit with error

 MOV     R4, #0				;no bytes left to copy

 TST     R11, #Background_Op		;are we running a background transfer?
 BEQ     _not_background_op		;NO
 BL      Find_ScatterList_Start		;YES, find start of scatterlist
 STR     R0, [R14, #-4]			;clear process active bit PRM2-604
 LDR     R12, ADFS_FileCore_Private	;notify FileCore transfer is complete
 ADR     R1, FileCore_Notify_Background
 MOV     R14, PC
 LDR     PC, [R1]

 ._not_background_op
 LDR     R1, DiscOp_CPSR		;restore flags
 BIC     R1, R1, #1 << 28		;clear V
 #if compile_RO > 311
   MSR     CPSR_f, R1
 #endif
 #if compile_RO == 311
   TEQP    R1, #0
 #endif
LDMFD   R13!, {R1, R9-R12, PC}




;DiscOp_Error_Exit
;-----------------
;Exits with a disc error, or pointer to error block for
;background transfers
;
;Entry:
;R0  = error
;R1  = pointer to start of scatterlist
;R3  = scatterlist that errored
;R4  = bytes remaining, negative if error occurred during background part
;R10 = disc address in bytes
;R11 = DiscOp R1

;References:
; www.riscosopen.org/wiki/documentation/show/FileCore Error Numbers
; PRM2-604 - scatterlist structure / returning errors during background transfer

.DiscOp_Error_Exit
 CMP     R0, #&80			;FileCore errors?
 BHS     _exit				;YES, exit with error in R0

 LDR     R9, ADFS_FS_Create_Block	;build error code into R0
 TST     R9, #New_Style_Errors		;is ADFS using new style errors?
 MOVEQ   R0, R0, LSL #24		;NO
 ORREQ   R0, R0, #1<<31			; |   set error flag
 ORREQ   R0, R0, R10, LSR #8		; |+  disc byte address/256
 ADRNE   R12, new_style_error_block	;YES
 STRNEB  R0, [R12, #1]			; |   store error code
 STRNE   R10, [R12, #4]			; |   disc address
 ORRNE   R0, R12, #1			; |+  set error flag

 TST     R11, #Background_Op		;are we processing a background transfer?
 BEQ     _exit				;NO, exit
 BL      Find_ScatterList_Start		;YES, find start of scatterlist

 CMP     R4, #0				;are we in the background part?
 MOVMI   R4, #0				;YES, clear foreground bytes remaining

 TST     R9, #New_Style_Errors		;is ADFS using new style errors?
 MOVNE   R10, R3, LSR #2		;YES, get bits 2-31 of scatter list
 STMNEDB R14, {R0, R10}			;|+   store error and scatter entry PRM2-604
 STMEQDB R14, {R0, R3}			;NO,  store error and scatter entry PRM2-604

 LDR     R12, ADFS_FileCore_Private
 ADR     R1, FileCore_Notify_Background
 MOV     R14, PC
 LDR     PC, [R1]			;notify FileCore transfer is complete

 ._exit
 LDR     R1, DiscOp_CPSR		;restore flags
 ORR     R1, R1, #1 << 28		;set V
 #if compile_RO > 311
   MSR     CPSR_f, R1
 #endif
 #if compile_RO == 311
   TEQP    R1, #0
 #endif
LDMFD   R13!, {R1, R9-R12, PC}




;DiscOp_Error
;------------
;Stores the error in R0 and exists via the correct method
;
;Entry:
; R0 = error number
;R11 = R1 as passed by FileCore

.DiscOp_Error
 TEMP    R2
 STR     R0, DiscOp_return_error
 LOCK    R2
 TST     R11, #ScatterList_Op
 BNE     DiscOp_ScatterList_exit
B       DiscOp_ReadWrite_Exit




;DiscOp 2
;--------
;Entry:
; R3 = pointer to buffer
; R4 = length in bytes
; R5 = pointer to disc record
; R9 = log2secsize
;R10 = disc address in bytes
;R11 = R1 as passed by FileCore

;Exit:
; R4  - bytes remaining on error, zero if successful
; R10 - updated

.DiscOp_Write
 ADR     R14, JFD_tmp
 LDR     R14, [R14, #JFD_Bit_Flags]
 TST     R14, #JFD_BF_WriteProtect	;is the disc write protected?
 MOVNE   R0, #Error_Write_Protected	;YES, return error
 BNE     DiscOp_Error

 TST     R11, #ScatterList_Op		;are we processing a scatterlist?
 BNE     _Write_ScatterList		;YES

 TEQ     R4, #0				;if no bytes requested, exit immediately
 BEQ     Entry_DiscOp_Exit

 ADR     R14, non_scatter_original
 STMIA   R14, {R3, R4}			;if we get a CRC, we exit with these in R3, R4
 ADR     R14, DiscOp_local_scatter
 STMIA   R14, {R3, R4}			;use scatterlist to perform read
 ADD     R3, R3, R4			;set R3 to final address
 TEMP    R0
 STR     R3, non_scatter_R3_return	;..and save so we can restore on exit from
 LOCK    R0
 MOV     R3, R14			;..the scatterlist code

;fall through to DiscOp_Write_ScatterList

;Entry:
; R11 - R1 as passed by FileCore

 ._Write_ScatterList
   LDMIA   R3, {R0, R12}		;read the address and length

   CMN     R0, #Scatter_List_End	;if <= -65536 the scatter list looped
#if compile_RO == 311
   ADDCS   R3, R3, R0			;...go back to the start of the list
   LDMCSIA R3, {R0, R12}		;...and load the next pair
#else
   TEQCS   R12, #0			;...and R12=0
   ADDEQ   R3, R3, R0			;...go back to the start of the list
   LDMEQIA R3, {R0, R12}		;...and load the next pair
#endif

   TEQ     R12, #0			;are we at the end of the list?
   BEQ     DiscOp_ScatterList_exit

   BL      Convert_Sector_to_Addr	;convert discaddr to our internal disc address
   BVS     DiscOp_ScatterList_exit	;exit on unrecoverable error
   MOV     R1, R14			;memory address

   CMP     R12, R2			;is this scatter entry more than one sector?
   MOVLS   R2, R12			;NO, we can read in one go

   TST     R11, #Background_Op		;are we running a background transfer?
   BNE     _P1				;YES, skip foreground limit check
     CMP     R2, R4
     MOVHI   R2, R4			;cap transfer to foreground limit
   ._P1

   BL      MemCopy
   ADR     R14, floppy_content_changed
   STR     R14, [R14, #0]		;ensure we flush the changes

   ADD     R10, R10, R2			;next disc sector
					;now update the address / bytes remaining
   SUBS    R12, R12, R2			;reduce remainder, have we finished scatter?
   STMNEIA R3, {R0, R12}		;NO, we need to stay on it
   STMEQIA R3!, {R0, R12}		;YES, go to next

   SUBS    R4, R4, R2			;reduce foreground remainder
   TSTEQ   R11, #Background_Op		;are we running a background transfer?
 BNE     _Write_ScatterList		;loop while bytes still remaining
B       DiscOp_ScatterList_exit




;DiscOp 3
;--------
;Entry:
; R1 = R1 as passed by FileCore
; R3 = pointer to buffer
; R5 = pointer to disc record
; R9 = log2secsize
;R10 = disc address in bytes
;R11 = R1 as passed by FileCore

;Exit:
;R2, R3 - preserved

.DiscOp_ReadTrack
 STMFD   R13!, {R2, R3}			;preserve R2, R3

 LDRB    R12, [R5, #DR_secspertrack]	;R12 = sectors per track
 MOV     R2, R10, LSR R9		;R10 = disc address in sectors
 M_DIV   R2, R12, R11			;R2 (Track) = R2/R12  R11=remainder (Sector)

 TST     R1, #DefectList_Op		;are we being asked for an ID list?
 LDREQ   R0, emulate_1772		;NO
 TEQEQ   R0, #1				; |  are we emulating a 1772
 BEQ     _Track_data			;YES, return the whole track

 AND     R9, R2, #1			;R9 = head
 MOV     R10, R2, LSR #1		;R1 = cylinder

 LDR     R0, ADF_Mounted
 TEQ     R0, #1				;ADF mounted?
 BEQ     _ADF				;YES, need to handle seperately

 LDR     R12, APD_Tracks		;track info pointer
 LDR     R12, [R12, R2, LSL #2]		;R12 = offset in sector info table

 CMN     R12, #1			;is the track pointer -1
 LDMEQFD R13!, {R2, R3}			;clear down stack
 LDREQ   R0, FDC_Error_Sector_Not_Found	;YES?
 BEQ     DiscOp_Error			;|+ unformatted, exit with error

					;now, read the sector ID's
 LDR     R0, APD_Sectors		;sector info pointer
 ADD     R12, R0, R12			;R12 = address of sector table
 MOV     R11, #0			;R11 will end up being secspertrack
 ._L1
   LDR     R0, [R12]			;load the sector ID
   CMN     R0, #1			;is it -1
   BEQ     _L1_exit			;YES, exit loop

   TST     R0, #bit_track_no		;explicit cylinder?
   LDRNEB  R1, [R12, #O_time]		;YES, read it from the time field

   STRB    R10, [R3], #1		;cylinder
   ADD     R11, R11, #1			;increase secspertrack
   STRB    R9, [R3], #1			;head
   LDRB    R1, [R12, #O_sector_no]	;load the sector no
   STRB    R1, [R3], #1
   LDRB    R1, [R12, #O_sector_length]	;load the sector length
   AND     R1, R1, #bits_sector_length	;mask it
   STRB    R1, [R3], #1

   TST     R0, #bit_slow_sector		;is this a slow sector?
   BLNE    read_slow_sector		;delay for sector read
   ADD     R12, R12, #8			;go to the next sector
 B     _L1

 ._L1_exit

; ADFS fails to update the discsize correctly on RO3.5+
; STRB  R11, [R5, #DR_secspertrack]	;update secspertrack in discrec
 BL      Identify_Disc
 LDR     R1, [R0, #DR_log2secsize]	;get disc record
 STR     R1, [R5, #DR_log2secsize]	;update disc record
 LDR     R1, os_version
 CMP     R1, #&350 << 8			;RO3.5+
 LDRLO   R1, [R0, #DR_discsize]
 STRLO   R1, [R5, #DR_discsize]

 LDMFD   R13!, {R2, R3}			;restore R2, R3

 SUBS    R0, R0, R0			;clear V
LDMFD   R13!, {R1, R9-R12, PC}


;make up sectors if we have an ADF file mounted

 ._ADF
 BL      Convert_Sector_to_Addr		;check track exists
 BVS     DiscOp_Error_Exit		;exit if it doesn't

 MOV     R12, #0
 LDRB    R10, [R5, #DR_log2secsize]
 SUB     R10, R10, #7			;R10 = sector size in 1772 format

 LDRB    R0, [R5, #DR_secspertrack]	;R0 = sectors per track
 ._L2
   STRB    R1, [R3], #1			;cylinder
   STRB    R9, [R3], #1			;head
   STRB    R12, [R3], #1		;sector no
   STRB    R10, [R3], #1		;sector size

   ADD     R11, R11, #1			;increase secspertrack
   ADD     R12, R12, #1			;next sector
   SUBS    R0, R0, #1			;until we run out of sectors
 BNE     _L2

 SUBS    R0, R0, R0			;clear V
 LDMFD   R13!, {R2, R3}
LDMFD   R13!, {R1, R9-R12, PC}


;_Track_data
;-----------
;return the whole track

._Track_data
 BL      Convert_Sector_to_Addr
 BVS     _Track_data_exit

;NEED TO CODE: Check sector returned is a DiscOp 3 data sector

 LDR     R2, [R14]			;get size of track
 MOV     R1, R3				;address to copy to
 ADD     R0, R14, #4			;address of data
 BL      MemCopy

 SUBS    R0, R0, R0			;clear V

 ._Track_data_exit
 LDMFD   R13!, {R2, R3}			;restore R2, R3
LDMFD   R13!, {R1, R9-R12, PC}




;Prevent_ScatterList_Adds
;------------------------
;Prevents additions to a background transfer list
;
;Entry:
; R1 = reason
;
;Exit:
; R0 - corrupt

.Prevent_ScatterList_Adds
 TST     R1, #Background_Op		;is it a background op?
 MOVEQ   PC, R14			;NO, exit

 STR     R14, [R13, #-4]!
 SWI     XOS_IntOff
 BL      Find_ScatterList_Start		;YES, find the start
 LDR     R0, [R14, #-4]			;load status word PRM2-604
 TST     R0, #1<<30			;are extensions allowed?
 BICNE   R0, R0, #1<<30			;YES, clear "process can be extended" flag
 STRNE   R0, [R14, #-4]
 SWI     XOS_IntOn

LDR     PC, [R13], #4




;Find_ScatterList_Start
;----------------------
;Returns the address of the first entry in a scatterlist
;
;Entry:
; R3 = scatterlist pointer
;
;Exit:
; R14 - pointer to start of scatterlist

.Find_ScatterList_Start
 STMFD   R13!, {R2-R4, R14}

 ._L1					;find the start of the scatter list
   LDMIA   R3!, {R2, R4}
   CMN     R2, #Scatter_List_End	;if address <= -65536
#if compile_RO == 311
 BCC     _L1
#else
   TEQCS   R4, #0			;.. and length 0
 BNE     _L1				;.. we've hit the end
#endif

 ADD     R3, R3, R2
 SUB     R14, R3, #8			;R14 is now start of list

LDMFD   R13!, {R2-R4, PC}





;capture_DiscOp_read
;-------------------
;Records DiscOp actions
;
;Entry:
; R1-R5 - as passed by FileCore
; {stack+8} - R1

;Exit:
; R0 - corrupt

.capture_DiscOp_read
 STMFD   R13!, {R1-R12, R14}

 BL      Prevent_ScatterList_Adds	;prevent scatterlist adds

 LDRB    R9, [R5, #DR_log2secsize]	;R9 = size of sector log2
 LDR     R0, ADFS_FS_Create_Block	;is ADFS running in sector mode?
 TST     R0, #ADFS_Sector_Aligned
 MOVNE   R2, R2, LSL R9			;YES, change R2 to bytes

 ADR     R8, recordStack
 STR     R1, [R8, #0*4]			;record R1 - reason
 STR     R2, [R8, #1*4]			;record R2 - disc address
 LDR     R10, [R5]			;record [R5, #0]
 STR     R10, [R8, #3*4]		;...log2secsize, secspertrack, heads, density
 LDR     R10, [R5, #DR_discsize]	;record [R5, #DR_discsize]
 STR     R10, [R8, #4*4]		;...disc size

 TST     R1, #ScatterList_Op		;is this a scatterlist?
 TSTNE   R1, #Background_Op		;YES, is it a background transfer?
 BEQ     _exit				;NO, R4 is already correct

 MOV     R4, #0				;reset byte counter
 ._L1
   LDMIA   R3, {R1, R12}		;read the address and length

   CMN     R1, #Scatter_List_End	;if <= -65536 the scatter list looped
#if compile_RO == 311
   ADDCS   R3, R3, R1			;...go back to the start of the list
   LDMCSIA R3, {R1, R12}		;...and load the next pair
#else
   TEQCS   R12, #0			;...and R12=0
   ADDEQ   R3, R3, R1			;...go back to the start of the list
   LDMEQIA R3, {R1, R12}		;...and load the next pair
#endif
   ADD     R3, R3, #8

   ADD     R4, R4, R12			;increase bytes read
   TEQ     R12, #0			;are we at the end of the list?
 BNE     _L1

 ._exit
 STR     R4, [R8, #2*4]			;record R4 - length

 MOV     R2, R8
 BL      record_discop			;write the DiscOp info to file

 LDMFD   R13!, {R1-R12, R14}
B       capture_DiscOp_read_return




;record_discop
;-------------
;Writes a disc op to the record file and flushes it to disc
;
;Entry:
; R2 - address of buffer containing 5 registers
;
;Exit:
; R2 - corrupted

.record_discop
 STMFD   R13!, {R0-R1, R3-R5, R12, R14}
 ADR     R12, VARS

 #if Recording_immediate == 0
   LDR     R0, [R12, #recording_ptr - VARS]	;load current pointer
   LDR     R3, [R12, #recording_addr - VARS]	;..and start of buffer
   SUB     R1, R0, R3
   TEQ     R1, #Max_Recording_Size		;is buffer full?
   LDMEQFD R13!, {R0-R1, R3-R5, R12, PC}	;YES, exit

   LDMIA   R2, {R1, R3, R4, R5, R14}
   STMIA   R0!, {R1, R3, R4, R5, R14}		;20 bytes per entry
   STR     R0, [R12, #recording_ptr - VARS]
 #endif

;@@@ next section for debugging purposes (displays DiscOp values)
#if Display_DiscOpValues == 1
 MOV     R4, R2
 LDR     R1, [R4, #0]
 writeB  R1
 writeS  ":"
 LDR     R1, [R4, #1*4]
 writeD  R1
 writeS  " "
 LDR     R1, [R4, #2*4]
 writeD  R1
 writeS  " "
 LDR     R1, [R4, #3*4]
 writeD  R1
 writeS  " "
 LDR     R1, [R4, #4*4]
 writeD  R1
 writeS  ""
 MOV     R2, R4
#endif
;@@@

;@@@ next section for debugging purposes (saves immediately)
#if Recording_immediate == 1
 BL      resetVectors			;reset key vectors
 ADR     R1, record_filename		;filename
 MOV     R0, #&CF			;open existing file
 SWI     XOS_Find			;PRM2-78
 MOVVS   R0, #&8F			;create new file
 SWIVS   XOS_Find			;PRM2-78
 BVS     _exit2				;exit on error
 MOV     R1, R0				;file handle

 MOV     R3, #5 * 4			;length to save
 MOV     R0, #1				;specified file pointer
 LDR     R4, [R12, #recording_ptr - VARS]	;load current pointer
 SWI     XOS_GBPB			;PRM2-68
 STRVC   R4, [R12, #recording_ptr - VARS]	;update pointer
 MOV     R0, #0				;close file
 SWI     XOS_Find			;PRM2-77
 ._exit2
 BL      resetVectors			;reset key vectors
#endif
;@@@

;@@@ next section for debugging purposes (slows if CTRL pressed)
#if CTRL_PauseDebug == 1
 MOV     R0, #202
 MOV     R1, #0
 MOV     R2, #&FF			;Read keyboard status
 SWI     XOS_Byte			;PRM1-914
 TST     R1, #1<<6			;was CTRL key pressed?
 LDMEQFD R13!, {R0-R1, R3-R5, R12, PC}	;NO, exit

 MOV     R0, #176
 MOV     R1, #0
 MOV     R2, #255
 SWI     XOS_Byte
 ADD     R3, R1, #1
 TEQ     R3, #50
 MOVEQ   R3, #0
 ._L_pause
     MOV     R0, #176			;pause for 50ms
     MOV     R1, #0
     MOV     R2, #255
     SWI     XOS_Byte
     TEQ     R1, R3
   BNE     _L_pause

   MOV     R0, #202			;pause whilst CTRL down
   MOV     R1, #0
   MOV     R2, #&FF			;Read keyboard status
   SWI     XOS_Byte			;PRM1-914
   TST     R1, #1<<6			;was CTRL key pressed?
 BNE     _L_pause			;YES, stay paused
#endif
;@@@
LDMFD   R13!, {R0-R1, R3-R5, R12, PC}
