        SUBT    > <wini>arm.FileSwitch.OSGBPB - Multi-byte data transfer

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; +                                                                           +
; +                     M U L T I P L E    S W I                              +
; +                                                                           +
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 
; MultipleEntry. Vectored SWI level entry
; =============
; 
; The dreaded heebeegeebee !

; In    r0b = Reason code
;       r1b = FileSwitch handle (if non-silly op)
;       r2  = memory address of data
;       r3  = number of bytes/names to transfer
;       r4  = file data pointer (if applicable)
;       r5  = buffer length (if applicable)
;       r6  = wildcard specifier

; Reason codes:
; =============

; Sensible data xfer ones
;       1: Write bytes from memory to file, using r4 = file data ptr
;       2: Write bytes from memory to file, using file sequential ptr
;       3: Read bytes to memory from file,  using r4 = file data ptr
;       4: Read bytes to memory from file,  using file sequential ptr

; Not-so-sensible ones
;       5: Read disc title
;       6: Read CSD name
;       7: Read LIB name
;       8: Read filenames from CSD

; New ones on Arthur
;       9: Read filenames from given directory
;      10: Read filenames and info from given directory

; Out   VC -> ok
;             r0, r1 preserved
;             r2 -> past last byte of memory used
;             r3 = number of bytes not transferred
;                CC if transfer complete, r3 = 0
;                CS if transfer could not be completed, r3 <> 0
;             r4 -> next position in file / directory
;             r5 = amount of buffer used
;       VS -> fail. If stream op, get handle with error too !

; NB. No EOF errors on OSGBPB

memadr  RN      r2
nbytes  RN      r3

MultipleEntry NewSwiEntry "r0-r1"

 [ debugosgbpb
 DREG  r0, "OSGBPB: rc ",cc
 TEQ   r0, #OSGBPB_ReadDiscName
 TEQNE r0, #OSGBPB_ReadCSDName
 TEQNE r0, #OSGBPB_ReadLIBName
 BEQ %FT00
 DREG r1, " ",cc
00
 DREG memadr, " ",cc
 DREG nbytes, " ",cc
 DREG fileptr, " ",cc
 TEQ r0, #OSGBPB_ReadDirEntries
 TEQNE r0, #OSGBPB_ReadDirEntriesInfo
 BNE %FT01
 DREG r5, " ",cc
 DSTRING r1, ", directory ",cc
 DSTRING r6, ", wildspec ",cc
01
 DLINE
 ]

10 ; Reentry point for below

        ADR     r14, gbpb_memadr        ; Save parms
        STMIA   r14, {memadr, nbytes, fileptr}

        ADR     lr, %FT50               ; Form a return address for routines
        CMP     r0, #OSGBPB_Max         ; NB. This has no psr bits in it!!!
        JTAB    r0, LO, OSGBPB          ; Good rc if LO
        B       %FA99
                                        ; Check reason codes against &.Hdr.File

        JTE     %FA99 ; Awkward - not defined at that macro level !

        JTE     Xfer_WriteBytes,        OSGBPB_WriteAtGiven
        JTE     Xfer_WriteBytes,        OSGBPB_WriteAtPTR
        JTE     Xfer_ReadBytes,         OSGBPB_ReadFromGiven
        JTE     Xfer_ReadBytes,         OSGBPB_ReadFromPTR

        JTE     Daft_ReadName,          OSGBPB_ReadDiscName
        JTE     Daft_ReadName,          OSGBPB_ReadCSDName
        JTE     Daft_ReadName,          OSGBPB_ReadLIBName
        JTE     Daft_ReadCSDEntries,    OSGBPB_ReadCSDEntries
        JTE     Daft_ReadDirEntries,    OSGBPB_ReadDirEntries
        JTE     Daft_ReadDirEntries,    OSGBPB_ReadDirEntriesInfo
        JTE     Daft_ReadDirEntries,    OSGBPB_ReadDirEntriesCatInfo

OSGBPB_Max * (.-$JumpTableName) :SHR: 2

; Routines need not preserve r0-r4 - these are always updated from parm block
; or preserved anyway

50      ADR     r14, gbpb_memadr        ; Update punter's registers
        LDMIA   r14, {memadr, nbytes, fileptr}

        CMP     nbytes, #1              ; If nbytes >= 1 then failed full xfer
                                        ; (xfer_bytes and Daft_ReadName/CSD)
 [ debugosgbpb
 BVS %FT80
 DREG memadr, "OSGBPB returns memadr ",cc
 DREG nbytes, " nbytes ",cc
 BCC %FT00
 DLINE " C",cc
00
 DREG fileptr, " fileptr ",cc
 LDR r0, [sp]
 TEQ r0, #OSGBPB_ReadDirEntries
 TEQNE r0, #OSGBPB_ReadDirEntriesInfo
 BNE %FT79
 DREG r5, " r5 ",cc
79
 DLINE
80
 ]
        LDR     r14, globalerror        ; Load this before destroying frame
9999    PullSwiEnv                      ; Destack all apart from lr
        B       FileSwitchExitSettingC  ; Set/Clear C for all GBPB ops now


99      CMP     r0, #&FF                ; Silly bits set ?
        ANDHI   r0, r0, #&FF            ; r0b is reason code
        BHI     %BT10                   ; Reenter

        addr    r14, ErrorBlock_BadOSGBPBReason
        B       %BA9999 ; SwiExit with r14 -> error

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Top level now sets/clears C flag on nbytes

; In    r0  = reason. Rest of data in gbpb_block
;       r1b = file handle

; Out   r1 trashed

Xfer_ReadBytes ENTRY "scb, $streaminfo"

        AND     r1, r1, #&FF            ; r1b is file handle
        ADR     r14, streamtable
        LDR     scb, [r14, r1, LSL #2]  ; Get scb^

        ReadStreamInfo

10 ; Reentry point for below

        EOR     r14, status, #scb_read  ; Must be set
        TST     r14, #scb_read :OR: scb_directory :OR: scb_EOF_pending :OR: scb_unallocated
        TSTEQ   r14, #scb_datalost :OR: scb_critical
        BNE     %FT40                   ; [strange bits set/clear]
                                        ; >>>a186<<< was wrong label

 [ False ; Slows us down too much
        Push    "r2, r5"                ; Always validate all of passsed buffer
        ADR     r14, gbpb_memadr        ; even though we may not use it all
        LDMIA   r14, {r2, r5}
        ADD     r5, r2, r5
        BL      ValidateR2R5_WriteToCore ; Don't conditional push/pull as they
        Pull    "r2, r5"                ; would go unbalanced if VS from ValReg
        BVS     %FT49
 ]

        TST     status, #scb_unbuffered
        BNE     %FT30                   ; >>>a186<<< was wrong label

        BL      BufferedMultipleRead

 [ appendhandle
49      BLVS    AppendHandleToError     ; For all errors in ReadMultiple
 ]
        EXIT


30      BL      UnbufferedMultiple
 [ appendhandle
        B       %BT49                   ; Catch errors on way out (code space)
 |
        EXIT
 ]


40 ; Complicated ReadBytes - flags set

        TST     status, #scb_unallocated        ; Not a stream ?
        BNE     %FT92

        TST     status, #scb_critical           ; Return - done nothing to file
        EXIT    NE

        TST     status, #scb_datalost           ; Cant read from dead file
        BNE     %FT93

        TST     status, #scb_EOF_pending        ; Always clear this on read
        BICNE   status, status, #scb_EOF_pending ; (unlike BGet)
        STRNE   status, scb_status
        BNE     %BT10                           ; Reenter

        TST     status, #scb_directory          ; Can't read from a directory
        BNE     %FT94

        BL      SetErrorNotOpenForReading       ; Must have no read access
 [ appendhandle
        B       %BT49
 |
        EXIT
 ]

; .............................................................................
; Top level now sets/clears C flag on nbytes

; In    r0  = reason. Rest of data in gbpb_block
;       r1b = handle

; Out   r1 trashed

Xfer_WriteBytes ALTENTRY

        AND     r1, r1, #&FF            ; r1b is file handle
        ADR     r14, streamtable
        LDR     scb, [r14, r1, LSL #2]  ; Get scb^

        ReadStreamInfo

60 ; Reentry point for below

        EOR     r14, status, #scb_write + scb_modified ; Must be/should be set
        TST     r14, #scb_write :OR: scb_directory :OR: scb_EOF_pending :OR: scb_modified :OR: scb_unallocated
        TSTEQ   r14, #scb_datalost :OR: scb_critical
        BNE     %FT90                   ; [strange bits set/clear]

 [ False ; Slows us down too much
        Push    "r2, r5"                ; Always validate all of passsed buffer
        ADR     r14, gbpb_memadr        ; even though we may not use it all
        LDMIA   r14, {r2, r5}
        ADD     r5, r2, r5
        BL      ValidateR2R5_ReadFromCore ; Don't conditional push/pull as they
        Pull    "r2, r5"                ; would go unbalanced if VS from ValReg
        BVS     %FT99
 ]

        TST     status, #scb_unbuffered
        BNE     %FT80                   ; >>>a186<< changed label to clarify

        BL      BufferedMultipleWrite

 [ appendhandle
99      BLVS    AppendHandleToError     ; For all errors in WriteMultiple
 ]
        EXIT


80      BL      UnbufferedMultiple
 [ appendhandle
        B       %BT99                   ; Catch errors on way out (code space)
 |
        EXIT
 ]


90 ; Complicated WriteBytes - flags set

        TST     status, #scb_unallocated        ; Not a stream ?
        BNE     %FT92

        TST     status, #scb_critical           ; Return - done nothing to file
        EXIT    NE

        TST     status, #scb_datalost           ; Can't write to dead file
        BNE     %FT93

        TST     status, #scb_EOF_pending        ; Always clear this on write
        BICNE   status, status, #scb_EOF_pending
        STRNE   status, scb_status
        BNE     %BT60                           ; Reenter

        TST     status, #scb_modified           ; Always modify stream
        ORREQ   status, status, #scb_modified
        STREQ   status, scb_status
        BEQ     %BT60                           ; Reenter

        TST     status, #scb_directory          ; Can't read from a directory
        BNE     %FT94

        BL      SetErrorNotOpenForUpdate        ; Must have no write access
 [ appendhandle
        B       %BT99
 |
        EXIT
 ]


92      BL      SetErrorChannel
 [ appendhandle
        B       %BT99
 |
        EXIT
 ]

93      BL      SetErrorDataLost
 [ appendhandle
        BVS     %BT99
 |
        EXIT    VS
 ]

94      BL      SetErrorStreamIsDirectory
 [ appendhandle
        B       %BT99
 |
        EXIT
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 
; UnbufferedMultiple
; ==================
; 
; Do file gbpb on an unbuffered stream, eg. vdu:

; In    r0 = reason, scb^ valid. Rest of data in gbpb_block

UnbufferedMultiple ENTRY "r5-r7" ; We can use r0-r4 here (restored by toplevel)

        TST     status, #scb_unbuffgbpb ; Can FS do this quickly ?
        BEQ     %FT01                   ; Nope !

 [ debugosgbpb
 DLINE "Doing fast unbuffered GBPB"
 ]
        BL      CallFSMultiple          ; Gets/updates block itself
        EXIT


01      MOV     r7, r0                  ; Remember GBPB op

        TEQ     r0, #OSGBPB_ReadFromGiven ; Need to reset PTR# before op ?
        TEQNE   r0, #OSGBPB_WriteAtGiven
        BNE     %FT05

        MOV     r0, #fsargs_SetPTR      ; Set PTR# unbuffered first
        LDR     r2, gbpb_fileptr
        BL      CallFSArgs
        EXIT    VS


05      ADR     r14, gbpb_memadr        ; memadr -> memory for transfer
        LDMIA   r14, {memadr, nbytes}   ; nbytes = number of bytes to transfer
        ADD     r5, memadr, nbytes      ; Calculate end address

        TEQ     r7, #OSGBPB_ReadFromGiven ; Read op [3..4] ?
        TEQNE   r7, #OSGBPB_ReadFromPTR
        BEQ     %FT65

; else drop into ... Writing to unbuffered file at seqptr

25      CMP     memadr, r5              ; Reached end address ?
        BEQ     %FT70                   ; Use common exit if so
        LDRB    r0, [memadr], #1        ; Get byte from memory++
        BL      CallFSBPut
        BVC     %BT25
        EXIT


; Reading from unbuffered file at seqptr

65      CMP     memadr, r5              ; Reached end address ?
        BEQ     %FT70                   ; Use common exit if so
        BL      CallFSBGet
        EXIT    VS
        STRCCB  r0, [memadr], #1        ; Put byte to memory++
        BCC     %BT65                   ; If CSet, byte invalid, EOF happening


; Common exit for read/write data ops

70      SUB     nbytes, r5, memadr      ; Require nbytes NOT transferred
        ADR     r14, gbpb_memadr
        STMIA   r14, {memadr, nbytes}   ; Update user's memadr, nbytes

        MOV     r0, #fsargs_ReadPTR     ; Read PTR# after operation
        BL      CallFSArgs
        STRVC   r2, gbpb_fileptr        ; Update user's file pointer
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; We can use r0-r2 as temporaries after preserving r1 (for error on way out)
; r3(status) isn't needed, so can use this
; r4(fileptr) will be updated by op
; Keep r5(extent)-r6(bufmask) ok !

 assert extent >= r5 ; Need to use r0-r3 freely here. Just status dead

; No need to invalidate caches as appropriate routines will do so

BufferedMultipleRead ENTRY "r1, bcb"

        TEQ     r0, #OSGBPB_ReadFromGiven ; Only difference between the two ops
        ADR     r14, gbpb_memadr
        LDMNEIA r14, {memadr, nbytes}   ; Use fileptr from stream
        LDMEQIA r14, {memadr, nbytes, fileptr}
                                        ; Correct if transfer off eof, no mods

        CMP     fileptr, extent         ; Nothing to do if startptr >= extent
        EXITS   HS                      ; No need to update the punter either
                                        ; Assumes VClear in entry lr

        SUB     r14, memadr, fileptr    ; Calc address in core where the byte
                                        ; at offset 0 in the file would be put
        STR     r14, memadr_filebase    ; (to add to file offsets later)

; Work out what the hell we are doing; ie. real nbytes for xfer

; if fileptr > extent
; then
; $(
;    nbytes_doing    := 0
;    return
; $)
; endptr := fileptr + nbytes
; if endptr > extent
; then
; $(
;    nbytes_doing    := extent-startptr
;    nbytes_notdoing := endptr-extent
;    endptr          := fileptr + nbytes_doing
; $)
; else
; $(
;    nbytes_doing    := nbytes
;    nbytes_notdoing := 0
; $)
; memadr  +:= nbytes_doing
; fileptr +:= nbytes_doing

        ADD     r0, fileptr, nbytes     ; endptr := startptr + nbytes
        SUBS    r0, r0, extent          ; nbytes_notdoing := endptr-extent
        SUBGT   nbytes, extent, fileptr ; nbytes_doing    := extent-startptr
        MOVLE   r0, #0                  ; nbytes_notdoing := 0
; ;;!!!  MOVLE   nbytes, nbytes          ; nbytes_doing    := nbytes

        CMP     nbytes, #0              ; Doing anything at all ? If not, quit
        EXIT    EQ                      ; leaving punter registers unmolested
                                        ; VClear

        STR     r0, gbpb_nbytes         ; He gets number not done

        ADD     r14, memadr, nbytes     ; endaddr := startaddr + nbytes_doing
        STR     r14, gbpb_memadr
        ADD     r14, fileptr, nbytes    ; endptr  := startptr  + nbytes_doing
        STR     r14, gbpb_fileptr
        STR     r14, scb_fileptr        ; Also update stream's copy of PTR

; Unfortunate side effect is that stream info remains updated even if error

; startptr = fileptr(r4), nbytes_doing = nbytes, punter's memory at memadr

 [ debugosgbpb
 DREG nbytes, "BufferedMultipleRead: nbytes_doing ",cc
 DREG fileptr, " from PTR ",cc
 DREG r14, ", endptr = "
 ]

; ********* Loop over valid partial, then whole, buffers at start *************

05      BL      FindFileBuffer          ; Valid buffer at startptr ?
        BNE     %FT10                   ; NE -> found: use that then

; If startptr isn't buffer aligned, then we'll need to get a buffer + use it

        TST     fileptr, bufmask        ; Doing aligned start ?
        BEQ     %FT20                   ; Include this buffer in middle + end

        BL      GetFileBuffer
        EXIT    VS

10 ; Valid buffer at startptr - supply as much data as possible from that

        AND     r0, fileptr, bufmask    ; Offset of start point in buffer
 [ debugosgbpb
 DREG r0, "Copying data to memory from buffer offset "
 ]
        ADR     r1, bcb_bufferdata
        ADD     r1, r1, r0              ; My address of start point
        SUB     r0, bufmask, r0         ; n_possible := bufsize - startoffset
        ADD     r0, r0, #1
        CMP     r0, nbytes              ; n_doing := min (n_possible, n_bytes)
        MOVHI   r0, nbytes
        Push    "r0, r2, r3"            ; Remember n_doing(r0) for after loop
                                        ; NB. This is never zero here !
        MOV     r3, r0
        BL      MoveBytes

        Pull    "r0, r2, r3"
        ADD     memadr, memadr, r0

        SUBS    nbytes, nbytes, r0      ; Completed whole xfer ?
        EXIT    EQ                      ; VClear

; Force aligned data transfer at next file position 0000..00FF -> 0100

        BIC     fileptr, fileptr, bufmask
        ADD     fileptr, fileptr, bufmask
        ADD     fileptr, fileptr, #1    ; File address of next whole buffer


; If multiple buffers ever arrive, we could loop back to %BT05 and try more


20 ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Done the start bit, memadr/fileptr -> start of whole memory/sectors for xfer

; If multiple buffers, loop down from end of whole buffer section

        Push    nbytes                  ; Remember you're a womble
        BICS    nbytes, nbytes, bufmask ; How many bytes in whole xfer ?
        ADD     r0, memadr, nbytes
        ADD     r1, fileptr, nbytes     ; Where the end is (may be here !)
        Push    "r0, r1"
        BEQ     %FA35                   ; Skip if no middle

 [ debugosgbpb
 DREG nbytes, "Doing whole buffer xfers: getting "
 ]
        BLNE    CallFSGetData           ; Send it directly to the punter
        Pull    "r0, r1, nbytes", VS    ; if any requested
        EXIT    VS

 [ debugosgbpb
 DLINE "Whole buffer section complete"
 ]

; ******** Use data if there's a modified buffer in whole block range *********

        LDR     bcb, scb_bcb            ; No buffers ?
        CMP     bcb, #Nowt
        BEQ     %FA35                   ; Suss end

; Again, assumption of single buffered world here

        LDR     r14, bcb_bufferbase     ; Is buffer in whole range ?
        ADD     r0, fileptr, nbytes     ; nbytes still whole buffer multiple
        CMP     r14, fileptr            ; (bp => fp) and (fp+nb > bp)
        CMPHS   r0, r14
        BLS     %FA35                   ; Suss end. NB. kinky conditionals

        LDR     r14, bcb_status
        TST     r14, #bcb_modified
        BEQ     %FA35                   ; Suss end

        LDR     r0, bcb_bufferbase      ; Use data from modified buffer
        ADD     r1, bufmask, #1
        BL      FillFromBuffer          ; Tolerable, as this case never
                                        ; really occurs in practise

; *********************** Partial buffer at end ? *****************************

35      Pull    "r0, r1, nbytes"
        ANDS    nbytes, nbytes, bufmask ; How much more left ?
        EXIT    EQ                      ; We've done it, boys !

        MOV     memadr, r0
        MOV     fileptr, r1             ; File position of end buffer

 [ debugosgbpb
 DREG nbytes, "Doing end section of ",cc
 DLINE " bytes"
 ]
        BL      FindFileBuffer          ; Valid buffer at endptr ?
        BLEQ    GetFileBuffer           ; NE -> found : use that then
        EXIT    VS

; Valid buffer at endptr - supply rest of data from that

 [ debugosgbpb
 DLINE "Copying data to memory from buffer offset 0"
 ]
        ADR     r1, bcb_bufferdata      ; Copy nbytes from offset 0 onwards
                                        ; NB. nbytes is never zero here !
        BL      MoveBytes               ; r2 -> punter memory
        EXIT                            ; VClear

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 
; FillFromBuffer
; ==============
; 
; Supply data from buffer to user memory (determined by memadr_filebase)

; In    bufmask, bcb^ valid, at right file address
;       r0 = start point in file
;       r1 = number of bytes to do

; Out   r1 = number of bytes done

FillFromBuffer ENTRY "r0-r3" ; Result poked into stack

 [ debugosgbpb
 DREG r0, "FillFromBuffer: fileptr ",cc
 DREG r1, ", nbytes_reqested "
 ]
        AND     r0, r0, bufmask         ; Offset in buffer

        LDR     r2, bcb_bufferbase      ; Base fileptr of buffer
        LDR     r14, memadr_filebase    ; Add punter base addr to this base ptr
        ADD     r2, r2, r14             ; Punter address of buffer start

        ADD     r14, bufmask, #1        ; Limit xfer to this buffer
        ADD     r3, r0, r1              ; start offset + nbytes_reqested
        CMP     r3, r14                 ; Over the end; if so reduce nbytes
        SUBHI   r3, r14, r0             ; nbytes := buffer size - start offset
        MOVS    r3, r1
        STR     r3, [sp, #4]            ; Otherwise do nbytes_requested (r1)
                                        ; Poke result into stack
        EXIT    EQ                      ; Exit if nothing to do. VClear

        ADR     r1, bcb_bufferdata      ; My address of buffer start
        ADD     r1, r1, r0              ; My address of xfer start
        ADD     r2, r2, r0              ; Punter address of xfer start

 [ debugosgbpb
 DREG r0, "Using buffered data up from offset ",cc
 DREG r2, " in buffer -> memory ",cc
 DREG r3, ", nbytes_doing "
 ]
        BL      MoveBytes               ; Corrupts r0-r3
        EXITS                           ; Assumes VClear in entry lr

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; We can use r0-r2 as temporaries after preserving r1 (for error on way out)
; r3(status) isn't needed, so can use this
; r4(fileptr) will be updated by op
; Keep r5-r6 ok !

; No need to invalidate caches as appropriate routines will do so

BufferedMultipleWrite ENTRY "r1, bcb"

        TEQ     r0, #OSGBPB_WriteAtGiven ; Only difference between the two ops
        LDREQ   fileptr, gbpb_fileptr

        LDR     nbytes, gbpb_nbytes

        ADDS    r1, fileptr, nbytes     ; endptr := startptr + nbytes
        BCS     %FA99                   ; [file too big]
        STR     r1, endptr              ; remember to rewrite scb_extent at end

; If we have a file buffer that will be totally invalidated by the operation
; then discard it now

        MOV     r0, fileptr
        BL      DiscardBuffersBetween   ; fileptr..endptr-1
        EXIT    VS

        CMP     r1, extent              ; Need to make file bigger ?
        BLS     %FT01

        BL      GBPBExtendFile          ; r1 still endptr
        EXIT    VS

01 ; File is now correct size to take data

        CMP     nbytes, #0              ; Doing anything at all ? If not, quit
        BEQ     %FA85                   ; (may have done 0 byte xfer extending)

        LDR     memadr, gbpb_memadr     ; Begin to need to know this now

        MOV     r14, #0                 ; nbytes_notdoing := 0 (always full)
        STR     r14, gbpb_nbytes
        ADD     r14, memadr, nbytes     ; endaddr := startaddr + nbytes_doing
        STR     r14, gbpb_memadr
        ADD     r14, fileptr, nbytes    ; endptr  := startptr  + nbytes_doing
        STR     r14, gbpb_fileptr

; ;;;;;;startptr = fileptr(r4), nbytes_doing = r3, punter's memory at r2 ; ;;;;;

 [ debugosgbpb
 DREG nbytes, "BufferedMultipleWrite: nbytes_doing ",cc
 DREG fileptr, " to PTR ",cc
 DREG r14, ", endptr = "
 ]

; ********* Loop over valid partial, then whole, buffers at start *************

05      BL      FindFileBuffer          ; Valid buffer at startptr ?
        BNE     %FT10                   ; NE -> found: use that then


; If startptr isn't buffer aligned, then we'll need to get a buffer + use it

        TST     fileptr, bufmask        ; Doing aligned start ?
        BEQ     %FT20                   ; Include this buffer in middle + end

        BL      GetFileBuffer           ; Only get underlay if fileptr<=scb_ext
        EXIT    VS                      ; (gets memory to use though !)
 [ debugosgbpbirq
 Push "r0, lr"
 MOV r0, psr
 TST r0, #I_bit
 MOVEQ r0, #"4"
 MOVNE r0, #"d"
 SWI XOS_WriteC
 Pull "r0, lr"
 ]


10 ; Valid buffer at startptr - write as much data as possible to that

        LDRB    r14, bcb_status         ; Buffer modified
        ORR     r14, r14, #bcb_modified
        STRB    r14, bcb_status

        AND     r0, fileptr, bufmask    ; Offset of start point in buffer
 [ debugosgbpb
 DREG r0, "Copying data from memory to buffer offset "
 ]
        ADR     r1, bcb_bufferdata
        ADD     r1, r1, r0              ; My address of start point
        SUB     r0, bufmask, r0         ; n_possible := bufsize - startoffset
        ADD     r0, r0, #1
        CMP     r0, nbytes              ; n_doing := min (n_possible, n_bytes)
        MOVHI   r0, nbytes
        Push    "r0, r2, r3"            ; Remember n_doing(r0) for after loop
                                        ; NB. This is never zero here !
        MOV     r3, r0
        Swap    r1, r2                  ; Exchange pointers
        BL      MoveBytes

        Pull    "r0, r2, r3"
        ADD     memadr, memadr, r0

        SUBS    nbytes, nbytes, r0      ; Completed whole xfer ?
        BEQ     %FA85                   ; VClear

; Force aligned data transfer at next file position 0000..00FF -> 0100

        BIC     fileptr, fileptr, bufmask
        ADD     fileptr, fileptr, bufmask
        ADD     fileptr, fileptr, #1    ; File address of next whole buffer


; If multiple buffers ever arrive, we could loop back to %BT05 any try more


; To get write-behind to go properly, flush this buffer before rather than
; after the whole buffer section as it is earlier in the file

 [ debugosgbpbirq
 Push "r0, lr"
 MOV r0, psr
 TST r0, #I_bit
 MOVEQ r0, #"5"
 MOVNE r0, #"e"
 SWI XOS_WriteC
 Pull "r0, lr"
 ]
        TST     nbytes, bufmask         ; Will we require a partial end buffer?
        BLNE    FlushBuffer             ; [yes] current bcb rather than fileptr

20 ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Done the start bit, memadr/fileptr -> start of whole memory/sectors for xfer

; If we had multiple buffers, we'd need to loop down from aligned end getting
; data out of them rather than off disc

        Push    nbytes                  ; Remember you're a womble
        BICS    nbytes, nbytes, bufmask ; How many bytes in whole xfer? psr imp
        ADD     r0, memadr, nbytes
        ADD     r1, fileptr, nbytes     ; Where the end is (may be here !)
        Push    "r0, r1"
        BEQ     %FA35                   ; Skip if no middle

 [ debugosgbpb
 DREG nbytes, "Doing whole buffer xfers: putting "
 ]
        BL      CallFSPutData           ; Get it directly from the punter
        Pull    "r0, r1, nbytes", VS    ; if any requested
        EXIT    VS

 [ debugosgbpb
 DLINE "Whole buffer section complete"
 ]

; *********************** Partial buffer at end ? *****************************

35      Pull    "r0, r1, nbytes"
        ANDS    nbytes, nbytes, bufmask ; How much more left ?
        BEQ     %FA85                   ; We've done it, boys !

        MOV     memadr, r0
        MOV     fileptr, r1             ; File position of end buffer

 [ debugosgbpb
 DREG nbytes, "Doing end section of ",cc
 DLINE " bytes"
 ]
        BL      GetFileBuffer           ; Only get underlay if fileptr<=scb_ext
        EXIT    VS                      ; (gets memory to use though !)

; Valid buffer at endptr - supply rest of data to that

        LDRB    r14, bcb_status         ; Buffer modified
        ORR     r14, r14, #bcb_modified
        STRB    r14, bcb_status

 [ debugosgbpb
 DLINE "Copying data from memory to buffer offset 0"
 ]
        MOV     r1, r2
        ADR     r2, bcb_bufferdata      ; Copy nbytes from offset 0 onwards
                                        ; NB. nbytes never zero here !

        BL      MoveBytes


85      LDR     r14, endptr             ; If new extent > old extent, rewrite
        CMP     r14, extent
        STRHI   r14, scb_extent
        LDR     r14, gbpb_fileptr       ; stream fileptr updated if no error
        STR     r14, scb_fileptr
        EXITS                           ; Assumes VClear in entry lr


99      addr    r0, ErrorBlock_FSFileTooBig
        BL      CopyError
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 
; GBPBExtendFile
; ==============

; In    fileptr = length of file to extend and fill with zeroes to
;       extent  = old extent of file
;       r1      = new extent of file (endptr)

; WriteMultiple needs to extend the file

GBPBExtendFile ENTRY "r0-r2, fileptr, extent"

; Make file large enough to take data - if file extension takes place, tell
; filing system the old extent as well as the new size, so only valid data gets
; moved on the disc if the file gets moved around

 [ debugosgbpb
 DREG r1, "WriteMultiple causing file extension, new endptr "
 ]
        ADD     r2, r1, bufmask         ; Round xx001 -> xx100, but not xx000
        BIC     r2, r2, bufmask
        BL      EnsureFileSize          ; Uses scb_extent
        EXIT    VS

; May have to write zeroes between old extent and start of block we're going to
; write if fileptr > extent

        CMP     fileptr, extent         ; Need to write block of zeroes ?
        EXITS   LS                      ; No - assumes VClear in entry lr

 [ debugstream
 DREG fileptr, "WriteMultiple extending with zeroes to fileptr ",cc
 DREG extent, " from old extent "
 ]
        TST     extent, bufmask
        MOVEQ   r2, extent              ; Start point in file
        BEQ     %FT20                   ; [no partial update needed]

; Old extent unaligned, so we need to write zeroes to that buffer

        MOV     fileptr, extent         ; Get buffer at old extent
        BL      EnsureBufferValid
        EXIT    VS

        AND     r0, extent, bufmask     ; Offset of old extent in buffer
        BL      ZeroBufferFromPosition  ; Clear from old extent to eob

        LDR     fileptr, [sp, #4*3]     ; reload fileptr
        BIC     r14, fileptr, bufmask   ; Is extent in same buffer as fileptr ?
        BIC     r2, extent, bufmask
        CMP     r14, r2
        EXIT    EQ                      ; [yes, so no more to do here]
                                        ; don't flush in this case, as we'll
                                        ; reload in just a minute to do first
                                        ; partial data xfer to this point

        ADD     r2, r2, bufmask         ; Start point in file = roundup(extent)
        ADD     r2, r2, #1

        BL      FlushBuffer             ; And flush to disc (reduces head
        EXIT    VS                      ; motion to do now rather than after
                                        ; WriteZeroes call and subsequent load)


20 ; Write zeroes to file ? extent now aligned. r2 = start point in file

        BIC     r14, fileptr, bufmask

        SUBS    r1, r14, r2             ; How much to do
        BLNE    ZeroFileFromPosition
        EXIT    VS

; Do we need to create a partial buffer full of zeroes at fileptr ?

        TST     fileptr, bufmask
        EXIT    EQ                      ; No

        BL      EnsureBufferValid

        MOVVC   r0, #0                  ; Zap the lot
        BLVC    ZeroBufferFromPosition
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; The wally GBPB ops - ReadDiscName, ReadCSDName and ReadLIBName

; In    r2 -> memory to put info

Daft_ReadName ENTRY "r2-r5, r6, fscb" ; GBPB saves r0-r1; FSFunc saves r6-wp

 assert (fsfunc_ReadDiscName-OSGBPB_ReadDiscName) = (fsfunc_ReadCSDName-OSGBPB_ReadCSDName)
 assert (fsfunc_ReadDiscName-OSGBPB_ReadDiscName) = (fsfunc_ReadLIBName-OSGBPB_ReadLIBName)

        LDR     fscb, tempfs            ; Always make op use current context
        MOV     r6, #0

        ADD     r0, r0, #(fsfunc_ReadDiscName-OSGBPB_ReadDiscName)
        BL      CallFSFunc_Given

        MOVVC   r14, #0                 ; Always tell punter transfer complete
        STRVC   r14, gbpb_nbytes
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 
; Daft_ReadCSDEntries
; ===================
; 
; Read entries out of CSD

; In    r2 -> memory to put info
;       r3 =  number of names to read
;       r4 =  offset in directory (starting at 0)

; Out   gbpb_nbytes  = number of names NOT read
;       gbpb_fileptr = next offset in directory

Daft_ReadCSDEntries ENTRY "r0-r5" ; GBPB saves r0-r1; FSFunc saves r6-wp

        MOV     r14, #0                 ; Use current context
        STR     r14, speciallength
        BL      SCopySpecialField
        EXIT    VS

        LDR     r14, tempfs             ; Use temp fs
        STR     r14, fsforthisop

        ADD     r14, r3,  r3, LSL #3    ; *9
        ADD     r14, r14, r3, LSL #1    ; *11
        ADD     r5, r2, r14             ; Check buffer for at least r3 names
                                        ; of size 10 (+1 for length byte)
        BL      ValidateR2R5_WriteToCore

        MOVVC   r0, #fsfunc_ReadDirEntries
        ADRVC   r1, %FT90       ; Directory is CSD
        MOVVC   r5, #&100000    ; Oozles of memory please
        BLVC    CallFSFunc
        BVS     %FT95           ; Deallocate exit

        MOV     r5, r3          ; Remember how many to mutate

        LDR     r14, gbpb_fileptr
        CMP     r3, #0          ; If none read, leave fileptr at end of dir
        MOVNE   r14, r4
        STRNE   r14, gbpb_fileptr

        LDR     r14, gbpb_nbytes
        SUB     r14, r14, r3    ; Return number of entries NOT read
        STR     r14, gbpb_nbytes

        MOV     r1, r2          ; First name^

50      SUBS    r5, r5, #1      ; Finished ?
        BMI     %FT95           ; VClear. Deallocate exit
        BL      strlen
        ADD     r14, r1, r3     ; Point to the zero
55      CMP     r14, r1         ; Done them all ?
        LDRNEB  r0, [r14, #-1]  ; Load char from byte below posn
        MOVEQ   r0, r3          ; Length byte if last one
        STRB    r0, [r14], #-1  ; Put in place, and decrement for next
        BNE     %BT55
        ADD     r1, r1, r3      ; Skip to next name
        ADD     r1, r1, #1
        B       %BT50
90
        DCB     "@", 0
        ALIGN

95      BL      SFreeSpecialField
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 
; Daft_ReadDirEntries
; ===================
; 
; Read entries out of a given directory, matching against given wildcarded name

; In    r1 -> directory name
;       r2 -> memory to put info
;       r3 =  number of names to read
;       r4 =  offset in directory (starting at 0)
;       r5 =  buffer length
;       r6 =  wildcard specifier (0 -> use '*')

; Out   r0-r6 preserved, gbpb_nbytes, gbpb_fileptr updated at this level
;       r3 at SWI level = number of names read (will be zero if eod)
;       r4 at SWI level = next offset in directory

Daft_ReadDirEntries ENTRY "r0-r10" ; FSFunc only preserves r6 up

; Common code to start us off

        CMP     r3, #0                  ; Silly request ?
        BEQ     Daft_ReadDirEntriesNullExit

        TST     r4, #(1 :SHL: 31)       ; Already at end ? (-ve)
        BNE     Daft_ReadDirEntriesNullExit

        ADD     r5, r2, r5              ; Check buffer
        BL      ValidateR2R5_WriteToCore
        LDRVC   r5, [sp, #4*5]

        BLVC    TransNameSetFSAndPolice
        EXIT    VS

        CMP     r0, #OSGBPB_ReadDirEntriesCatInfo
        MOVEQ   r0, #fsfunc_CatalogObjects
        SUBNE   r0, r0, #(OSGBPB_ReadDirEntries-fsfunc_ReadDirEntries)

05 ; Reentry point for below loop

        Push    "r0-r2, r5"             ; FSFunc preserves r6 up
        BL      CallFSFunc
        Pull    "r0-r2, r5"
        BVS     Daft_ReadDirEntriesDeallocate
 [ debugdaftgbpb
 DREG r3, "Returned ",cc
 DREG r4, " names; next dir position "
 ]

        STR     r4, gbpb_fileptr        ; Update punter r4 here, r3 after match

        CMP     r3, #0                  ; Nothing from filing system ?
        BNE     %FT10

        CMP     r4, #-1                 ; All done ?
        BEQ     Daft_ReadDirEntriesExit ; Exit updating punter r3, deall string

        addr    r0, ErrorBlock_BuffOverflow ; r3 = 0, r4 <> -1 -> error !
        BL      CopyError
        B       Daft_ReadDirEntriesDeallocate


10      MOV     r9, r3                  ; Number of names returned
        MOV     r3, #0                  ; How many matched so far
        LDR     r1, [sp, #4*2]          ; Start of buffer. Keep as dst pointer
        MOV     r2, r1                  ; Keep as src pointer

        LDR     r14, [sp]               ; What was it again ?
        CMP     r14, #OSGBPB_ReadDirEntriesInfo
        BEQ     Daft_ReadDirEntriesInfo_Check ; Diverge now !!!

        CMP     r14, #OSGBPB_ReadDirEntriesCatInfo
        BEQ     Daft_ReadDirEntriesCatInfo_Check

; .............................................................................

Daft_ReadDirEntries_Check ; NOROUT; Match names against wild spec

50 ; Loop over returned names, copying down if matched

        LDR     r0, [sp, #4*6]          ; Wild spec
        MOV     r4, r2                  ; Filename^
        BL      TryWildMatch            ; EQ -> matches (corrupts r0, r4)
 [ debugdaftgbpb
 BNE %FT00
 DLINE " no"
00
 DLINE " match"
 ]

        MOV     r4, r2                  ; Remember this block^
        BL      AdvanceOverR2           ; Always move r2 ! Preserves flags
        BNE     %FT65

        ADD     r3, r3, #1              ; Another one matched

60      LDRB    r14, [r4], #1           ; Copy string
        STRB    r14, [r1], #1
        CMP     r14, #0
        BNE     %BT60                   ; r1, r2 -> next strings

65      SUBS    r9, r9, #1              ; Finished ?
        BNE     %BT50                   ; Loop if not

        B       Daft_ReadDirEntriesExit

; .............................................................................

Daft_ReadDirEntriesInfo_Check ROUT ; Match names against wild spec

50 ; Loop over returned names, copying down if matched

        LDR     r0, [sp, #4*6]          ; Wild spec
        ADD     r4, r2, #20             ; Filename^
        BL      TryWildMatch            ; EQ -> matches (corrupts r0, r4)
 [ debugdaftgbpb
 BNE %FT00
 DLINE " no"
00
 DLINE " match"
 ]

        MOV     r4, r2                  ; Remember this block^
        ADD     r2, r2, #20             ; Skip info fields
        BL      AdvanceOverR2           ; Always move r2 ! Preserves flags
        BNE     %FT65

        ADD     r3, r3, #1              ; Another one matched

        LDMIA   r4!, {r0, r5-r7, r14}   ; Copy info fields
        STMIA   r1!, {r0, r5-r7, r14}

60      LDRB    r14, [r4], #1           ; Copy string
        STRB    r14, [r1], #1
        CMP     r14, #0
        BNE     %BT60                   ; r1, r2 -> near next records

        ADD     r1, r1, #3              ; ALIGN dst after filename copy
        BIC     r1, r1, #3

65      ADD     r2, r2, #3              ; ALIGN src after filename copy / skip
        BIC     r2, r2, #3

        SUBS    r9, r9, #1              ; Finished ?
        BNE     %BT50                   ; Loop if not

        B       Daft_ReadDirEntriesExit

; .............................................................................

Daft_ReadDirEntriesCatInfo_Check ROUT ; Match names against wild spec

50 ; Loop over returned names, copying down if matched

        LDR     r0, [sp, #4*6]          ; Wild spec
        ADD     r4, r2, #20+4+5         ; Filename^
        BL      TryWildMatch            ; EQ -> matches (corrupts r0, r4)
 [ debugdaftgbpb
 BNE %FT00
 DLINE " no"
00
 DLINE " match"
 ]

        MOV     r4, r2                  ; Remember this block^
        ADD     r2, r2, #20+4+5         ; Skip info fields
        BL      AdvanceOverR2           ; Always move r2 ! Preserves flags
        BNE     %FT65

        ADD     r3, r3, #1              ; Another one matched

        LDMIA   r4!, {r0, r5-r8, r10, r14} ; Copy info fields
        STMIA   r1!, {r0, r5-r8, r10, r14}
        LDRB    r14, [r4], #1           ; Top byte of time
        STRB    r14, [r1], #1

60      LDRB    r14, [r4], #1           ; Copy string
        STRB    r14, [r1], #1
        CMP     r14, #0
        BNE     %BT60                   ; r1, r2 -> near next records

        ADD     r1, r1, #3              ; ALIGN dst after filename copy
        BIC     r1, r1, #3

65      ADD     r2, r2, #3              ; ALIGN src after filename copy / skip
        BIC     r2, r2, #3

        SUBS    r9, r9, #1              ; Finished ?
        BNE     %BT50                   ; Loop if not

; .............................................................................

Daft_ReadDirEntriesExit

 [ debugdaftgbpb
 DREG r3, "Number of items matched = "
 ]
        STR     r3, gbpb_nbytes         ; Update punter r3

Daft_ReadDirEntriesDeallocate

        BL      SFreePassedFilenameAndSpecial
        EXIT


Daft_ReadDirEntriesNullExit

        MOV     r3, #0
        MOV     r4, #-1
        ADR     r14, gbpb_nbytes
        STMIA   r14, {r3, r4}
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Point to item after string term. Must preserve flags

; r2 updated

AdvanceOverR2 ENTRY

01      LDRB    r14, [r2], #1
        CMP     r14, #space             ; Make it general, CtrlChar term
        BHS     %BT01
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 -> wild spec (0 -> use '*')
;       r4 -> filename

TryWildMatch ENTRY "r10, r11"

        TEQ     r0, #0
        addr    r0, fsw_wildstar, EQ

 [ AssemblingArthur
        BL      WildMatch
 |
        BL      xyzzy_WildMatch
 ]
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 
; MoveBytes(source, dest, size in bytes) - fast data copier from RCM
; =========

; SKS Reordered registers and order of copying to suit FileSwitch

; ** Not yet optimised to do transfers to make most of 1N,3S feature of MEMC **

; In:   r1 = src^ (byte address)
;       r2 = dst^ (byte address)
;       r3 = count (byte count - never zero!)

; Out:  r0-r3, lr corrupt

mbsrc1   RN 0
mbsrcptr RN 1
mbdstptr RN 2
mbcnt    RN 3
mbsrc2   RN 14                          ; Note deviancy, so care in LDM/STM
mbsrc3   RN 4
mbsrc4   RN 5
mbsrc5   RN 6
mbsrc6   RN 7
mbsrc7   RN 8
mbsrc8   RN 9
mbsrc9   RN 10
mbshftL  RN 11                          ; These two go at end to save a word
mbshftR  RN 12                          ; and an extra Pull lr!

MoveBytes ENTRY

 [ debugosgbpb
 DREG r1, "Copying data from ",cc
 DREG r2, " to ",cc
 DREG r3, ", nbytes_doing "
 ]
        TST     mbdstptr, #3
        BNE     MovByt100               ; [dst^ not word aligned]

MovByt20 ; dst^ now word aligned. branched back to from below

        TST     mbsrcptr, #3
        BNE     MovByt200               ; [src^ not word aligned]

; src^ & dst^ are now both word aligned
; cnt is a byte value (may not be a whole number of words)

; Quick sort out of what we've got left to do

        SUBS    mbcnt, mbcnt, #4*4      ; Four whole words to do (or more) ?
        BLT     MovByt40                ; [no]

        SUBS    mbcnt, mbcnt, #8*4-4*4  ; Eight whole words to do (or more) ?
        BLT     MovByt30                ; [no]

        Push    "mbsrc3-mbsrc8"         ; Push some more registers
MovByt25
 [ debugosgbpb
 DREG mbsrcptr,"Copying 8 words from "
 ]
        LDMIA   mbsrcptr!, {mbsrc1, mbsrc3-mbsrc8, mbsrc2} ; NB. Order!
        STMIA   mbdstptr!, {mbsrc1, mbsrc3-mbsrc8, mbsrc2}

        SUBS    mbcnt, mbcnt, #8*4
        BGE     MovByt25                ; [do another 8 words]
        Pull    "mbsrc3-mbsrc8"         ; Outside loop (silly otherwise)

        CMP     mbcnt, #-8*4            ; Quick test rather than chaining down
        EXIT    EQ                      ; [finished]


MovByt30
        ADDS    mbcnt, mbcnt, #8*4-4*4  ; Four whole words to do ?
        BLT     MovByt40

        Push    "mbsrc3-mbsrc4"         ; Push some more registers

 [ debugosgbpb
 DREG mbsrcptr,"Copying 4 words from "
 ]
        LDMIA   mbsrcptr!, {mbsrc1, mbsrc3-mbsrc4, mbsrc2} ; NB. Order!
        STMIA   mbdstptr!, {mbsrc1, mbsrc3-mbsrc4, mbsrc2}

        Pull    "mbsrc3-mbsrc4"

        EXIT    EQ                      ; [finished]
        SUB     mbcnt, mbcnt, #4*4


MovByt40
        ADDS    mbcnt, mbcnt, #4*4-2*4  ; Two whole words to do ?
        BLT     MovByt50

 [ debugosgbpb
 DREG mbsrcptr,"Copying 2 words from "
 ]
        LDMIA   mbsrcptr!, {mbsrc1, mbsrc2}
        STMIA   mbdstptr!, {mbsrc1, mbsrc2}

        EXIT    EQ                      ; [finished]
        SUB     mbcnt, mbcnt, #2*4


MovByt50
        ADDS    mbcnt, mbcnt, #2*4-1*4  ; One whole word to do ?
        BLT     MovByt60

 [ debugosgbpb
 DREG mbsrcptr,"Copying 1 word from "
 ]
        LDR     mbsrc1, [mbsrcptr], #4
        STR     mbsrc1, [mbdstptr], #4

        EXIT    EQ                      ; [finished]
        SUB     mbcnt, mbcnt, #1*4


MovByt60
        ADDS    mbcnt, mbcnt, #1*4-0*4  ; No more to do ?
        EXIT    EQ                      ; [finished]


        LDR     mbsrc1, [mbsrcptr]      ; Store remaining 1, 2 or 3 bytes
MovByt70
 [ debugosgbpb
 DREG mbsrcptr,"Copying 1 byte from "
 ]
        STRB    mbsrc1, [mbdstptr], #1
        MOV     mbsrc1, mbsrc1, LSR #8
        SUBS    mbcnt, mbcnt, #1
        BGT     MovByt70

        EXIT


; Initial dest^ not word aligned. Loop doing bytes (1,2 or 3) until it is

MovByt100
        LDRB    mbsrc1, [mbsrcptr], #1
        STRB    mbsrc1, [mbdstptr], #1
        SUBS    mbcnt, mbcnt, #1
        EXIT    EQ                      ; Finished after 1..3 bytes
        TST     mbdstptr, #3
        BNE     MovByt100

        B       MovByt20                ; Back to mainline code



MovByt200 ; dst^ now word aligned, but src^ isn't

 ASSERT "$Proc_RegList" = "" ; ie. just lr stacked at the moment
Proc_RegList SETS "mbshftL, mbshftR"

        Push    "mbshftL, mbshftR"      ; Need more registers this section

        AND     mbshftR, mbsrcptr, #3   ; Offset
        BIC     mbsrcptr, mbsrcptr, #3  ; Align src^

        MOV     mbshftR, mbshftR, LSL #3 ; rshft = 0, 8, 16 or 24 only
        RSB     mbshftL, mbshftR, #32    ; lshft = 32, 24, 16 or 8  only

        LDR     mbsrc1, [mbsrcptr], #4
        MOV     mbsrc1, mbsrc1, LSR mbshftR ; Always have mbsrc1 prepared

; Quick sort out of what we've got left to do

        SUBS    mbcnt, mbcnt, #4*4      ; Four whole words to do (or more) ?
        BLT     MovByt240               ; [no]

        SUBS    mbcnt, mbcnt, #8*4-4*4  ; Eight whole words to do (or more) ?
        BLT     MovByt230               ; [no]

        Push    "mbsrc3-mbsrc9"         ; Push some more registers
MovByt225
        LDMIA   mbsrcptr!, {mbsrc3-mbsrc9, mbsrc2} ; NB. Order!
        ORR     mbsrc1, mbsrc1, mbsrc3, LSL mbshftL

        MOV     mbsrc3, mbsrc3, LSR mbshftR
        ORR     mbsrc3, mbsrc3, mbsrc4, LSL mbshftL

        MOV     mbsrc4, mbsrc4, LSR mbshftR
        ORR     mbsrc4, mbsrc4, mbsrc5, LSL mbshftL

        MOV     mbsrc5, mbsrc5, LSR mbshftR
        ORR     mbsrc5, mbsrc5, mbsrc6, LSL mbshftL

        MOV     mbsrc6, mbsrc6, LSR mbshftR
        ORR     mbsrc6, mbsrc6, mbsrc7, LSL mbshftL

        MOV     mbsrc7, mbsrc7, LSR mbshftR
        ORR     mbsrc7, mbsrc7, mbsrc8, LSL mbshftL

        MOV     mbsrc8, mbsrc8, LSR mbshftR
        ORR     mbsrc8, mbsrc8, mbsrc9, LSL mbshftL

        MOV     mbsrc9, mbsrc9, LSR mbshftR
        ORR     mbsrc9, mbsrc9, mbsrc2, LSL mbshftL

        STMIA   mbdstptr!, {mbsrc1, mbsrc3-mbsrc9}

        MOV     mbsrc1, mbsrc2, LSR mbshftR ; Keep mbsrc1 prepared

        SUBS    mbcnt, mbcnt, #8*4
        BGE     MovByt225               ; [do another 8 words]
        Pull    "mbsrc3-mbsrc9"

        CMP     mbcnt, #-8*4            ; Quick test rather than chaining down
        EXIT    EQ                      ; [finished]


MovByt230
        ADDS    mbcnt, mbcnt, #8*4-4*4  ; Four whole words to do ?
        BLT     MovByt240

        Push    "mbsrc3-mbsrc5"         ; Push some more registers
        LDMIA   mbsrcptr!, {mbsrc3-mbsrc5, mbsrc2} ; NB. Order!
        ORR     mbsrc1, mbsrc1, mbsrc3, LSL mbshftL

        MOV     mbsrc3, mbsrc3, LSR mbshftR
        ORR     mbsrc3, mbsrc3, mbsrc4, LSL mbshftL

        MOV     mbsrc4, mbsrc4, LSR mbshftR
        ORR     mbsrc4, mbsrc4, mbsrc5, LSL mbshftL

        MOV     mbsrc5, mbsrc5, LSR mbshftR
        ORR     mbsrc5, mbsrc5, mbsrc2, LSL mbshftL

        STMIA   mbdstptr!, {mbsrc1, mbsrc3-mbsrc5}
        Pull    "mbsrc3-mbsrc5"

        EXIT    EQ                      ; [finished]
        SUB     mbcnt, mbcnt, #4*4
        MOV     mbsrc1, mbsrc2, LSR mbshftR ; Keep mbsrc1 prepared


MovByt240
        ADDS    mbcnt, mbcnt, #2*4      ; Two whole words to do ?
        BLT     MovByt250

        Push    "mbsrc3"
        LDMIA   mbsrcptr!, {mbsrc3, mbsrc2} ; NB. Order!
        ORR     mbsrc1, mbsrc1, mbsrc3, LSL mbshftL

        MOV     mbsrc3, mbsrc3, LSR mbshftR
        ORR     mbsrc3, mbsrc3, mbsrc2, LSL mbshftL

        STMIA   mbdstptr!, {mbsrc1, mbsrc3}
        Pull    "mbsrc3"

        EXIT    EQ                      ; [finished]
        SUB     mbcnt, mbcnt, #2*4
        MOV     mbsrc1, mbsrc2, LSR mbshftR ; Keep mbsrc1 prepared


MovByt250
        ADDS    mbcnt, mbcnt, #2*4-1*4  ; One whole word to do ?
        BLT     MovByt260

        LDR     mbsrc2, [mbsrcptr], #4
        ORR     mbsrc1, mbsrc1, mbsrc2, LSL mbshftL
        STR     mbsrc1, [mbdstptr], #4

        EXIT    EQ                      ; [finished]
        SUB     mbcnt, mbcnt, #1*4
        MOV     mbsrc1, mbsrc2, LSR mbshftR ; Keep mbsrc1 prepared


MovByt260
        ADDS    mbcnt, mbcnt, #1*4-0*4
        EXIT    EQ                      ; [finished]


        LDR     mbsrc2, [mbsrcptr]      ; Store remaining 1, 2 or 3 bytes
        ORR     mbsrc1, mbsrc1, mbsrc2, LSL mbshftL
MovByt270
        STRB    mbsrc1, [mbdstptr], #1
        MOV     mbsrc1, mbsrc1, LSR #8
        SUBS    mbcnt, mbcnt, #1
        BGT     MovByt270

        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        LTORG

        LNK     $fileprefix.OSArgs
