; Copyright 1996 Acorn Computers Ltd
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
        TTL   => ChangeDyn

;******************************************************************************
; ChangeDynamic SWI
; In  : R0 =  0 => System Heap,
;             1 => RMA
;             2 => Screen
;             3 => Sprite area
;             4 => Font cache
;             5 => RAM disc
;             6 => Free pool
;       R1 = no of bytes to change by
;
; Out : V set if CAO in AplWork or couldn't move all the bytes requested.
;       R1 set to bytes moved.
;******************************************************************************

; The following flag controls the operation of ReadCMOSAndConfigure and FudgeConfigureRMA
;
; If false, then no memory is in the free pool to start off, and these routines just allocate pages
; starting at page R2, and update this on exit.
;
; If true, then routine InitDynamicAreas initially moves all non-static free memory into the free pool,
; and then the above routines just take pages off the end of that.

        GBLL    GetPagesFromFreePool    ; whether ReadCMOSAndConfigure extract pages from the free pool
GetPagesFromFreePool SETL {TRUE}

AP_AppSpace     *       0                                       ; user r/w, CB
AP_SysHeap      *       0                                       ; user r/w, CB
AP_RMA          *       0                                       ; user r/w, CB
AP_Screen       *       0 :OR: DynAreaFlags_NotCacheable :OR: DynAreaFlags_DoublyMapped :OR: DynAreaFlags_NeedsSpecificPages
AP_Sprites      *       0                                       ; user r/w, CB
AP_FontArea     *       2                                       ; user none, CB
AP_RAMDisc      *       2 :OR: DynAreaFlags_NotCacheable        ; user none, ~CB (poor performance for current StrongARMs)
AP_RAMDisc_SA   *       2                                       ; user none,
AP_Duff         *       2 :OR: DynAreaFlags_NotCacheable :OR: DynAreaFlags_NotBufferable ; user none, ~C~B
AP_FreePool     *       2 :OR: DynAreaFlags_NotCacheable        ; user none, ~CB
AP_CursorChunk  *       1 :OR: DynAreaFlags_NotCacheable :OR: DynAreaFlags_NotBufferable :OR: PageFlags_Unavailable
AP_PageZero     *       0

AP_L2PT         *       2 :OR: DynAreaFlags_NotCacheable :OR: DynAreaFlags_NotBufferable ; user none, ~C~B
AP_L1PT         *       AP_L2PT :OR: PageFlags_Unavailable
AP_UndStackSoftCam *    PageFlags_Unavailable

  [ DA_Batman
ChangeDyn_Batcall    * -3               ; special DA number to select Batman usage of OS_ChangeDynamicArea
  ]
ChangeDyn_FreeAndApl * -2               ; special reason code for when we're sucking out of free pool and apl space
ChangeDyn_AplSpace   * -1
ChangeDyn_SysHeap    * 0
ChangeDyn_RMA        * 1
ChangeDyn_Screen     * 2
ChangeDyn_SpriteArea * 3
ChangeDyn_FontArea   * 4
ChangeDyn_RamFS      * 5
ChangeDyn_FreePool   * 6
ChangeDyn_MaxArea    * 6

; Area handler reason codes

DAHandler_PreGrow       *       0
DAHandler_PostGrow      *       1
DAHandler_PreShrink     *       2
DAHandler_PostShrink    *       3
 [ ShrinkableDAs
DAHandler_TestShrink    *       4       ; new reason added to find amount area could shrink by
 ]

; Number of entries in page block on stack

NumPageBlockEntries *   63
PageBlockSize   *       NumPageBlockEntries * 12
PageBlockChunk  *       NumPageBlockEntries * 4096

;
; mjs - performance enhancements (from Ursula, merged into HALised kernel June 2001)
; Workspace for acceleration of operations on a DA, by reducing need to traverse DA list.
;
; - accelerates allocating non-quick DA numbers to O(n) instead of laughable O(n*n), where n is no. of DAs
; - accelerates enumeration to O(n) instead of laughable O(n*n)
; - allocation of a quick handle (DA number) is O(1)
; - access of a DA node from a quick handle is O(1)
; - access of a DA node from a non-quick handle is O(1), if it repeats the most recent non-quick handle access (else O(n))
;
; - creation of a DA still has some O(n) work (requires search for address space), but is now rather quicker
; - removal of a DA is still O(n) (requires traversal of list in order to get previous node)
; - other uses of a DA with a quick handle (eg. get info, change size) avoid any O(n) work
;
; - all system handles will be quick.
; - non-system handles will be quick, except for very large numbers of DAs, or silly modules like the Wimp who insist on
;   their own silly DA number (the latter can still benefit from LastTreacleHandle - see below)
;
; Limitations:
; - does not allow anyone to choose their own DA number that clashes with the quick handle set - should not
;   be a problem since choosing own number reserved for Acorn use
; - does not allow anyone to renumber a DA with a quick handle - again, reserved for system use
; - DA names will be truncated to a maximum of 31 characters (or as defined below)
;
                                  GBLL DynArea_QuickHandles
DynArea_QuickHandles              SETL {TRUE}
;
      ;various bad things happen if DynArea_QuickHandles is FALSE (eg. some new API disappears)
      ;should remove FALSE build option to simplify source code next time kernel is updated (kept for reference/testing now)
      ASSERT DynArea_QuickHandles

                                  GBLL DynArea_NullNamePtrMeansHexString
DynArea_NullNamePtrMeansHexString SETL {TRUE} :LAND: DynArea_QuickHandles
;
  [ DynArea_QuickHandles
DynArea_MaxNameLength     * 31                      ;maximum length of DA name, excluding terminator (multiple of 4, -1)
DynArea_NumQHandles       * 256                     ;maximum no. of non-system quick handles available simultaneously
;
                          ^  0,R11
DynArea_TreacleGuess      # 4                       ;guess for next non-quick handle to allocate, if needed, is TreacleGuess+1
DynArea_CreatingHandle    # 4                       ;handle proposed but not yet committed, during DynArea_Create, or -1 if none
DynArea_CreatingPtr       # 4                       ;ptr to proposed DANode during DynArea_Create (invalid if CreatingHandle = -1)
DynArea_LastTreacleHandle # 4                       ;last non-quick handle accessed by a client (usually the Wimp), or -1 if none
DynArea_LastTreaclePtr    # 4                       ;ptr to DANode for last non-quick handle accessed (invalid if LastTreacleHandle = -1)
DynArea_LastEnumHandle    # 4                       ;last handle enumerated, or -1 if none
DynArea_LastEnumPtr       # 4                       ;ptr to DANode for last handle enumerated (invalid if LastEnumHandle = -1)
DynArea_ShrinkableSubList # 4                       ;sub list of dynamic areas that are Shrinkable (0 if none)
DynArea_OD6Signature      # 4                       ;signature of changes to non-system DAs since last call to OS_DynamicArea 6
                                                    ;bit  0 = 1 if any DAs have been created
                                                    ;bit  1 = 1 if any DAs have been removed
                                                    ;bit  2 = 1 if any DAs have been resized (excluding grow or shrink at creation or removal)
                                                    ;bit  3 = 1 if any DAs have been renumbered
                                                    ;bits 4-30   reserved (0)
                                                    ;bit 31 = 1 if next resize is not to update signature (used during create, remove)
DynArea_OD6PrevSignature  # 4                       ;previous signature, used to distinguish single from multiple changes
DynArea_OD6Handle         # 4                       ;handle of last DA that affected signature
DynArea_OD8Clamp1         # 4                       ;clamp value on area max size for OS_DynamicArea 0 with R5 = -1
                                                    ;(default -1, set by R1 of OS_DynamicArea 8)
DynArea_OD8Clamp2         # 4                       ;clamp value on area max size for OS_DynamicArea 0 with R5 > 0 (not Sparse)
                                                    ;(default -1, set by R2 of OS_DynamicArea 8)
DynArea_OD8Clamp3         # 4                       ;clamp value on area max size for OS_DynamicArea 0 for a Sparse area
                                                    ;(default 4G-4k, set by R3 of OS_DynamicArea 8)
DynArea_SortedList        # 4                       ;alphabetically sorted list of non-system areas, or 0 if none
DynArea_SysQHandleArray   # 4*(ChangeDyn_MaxArea+1) ;for system areas 0..MaxArea, word = ptr to DANode, or 0 if not created yet
DynArea_FreeQHandles      # 4                       ;index of first free quick handle, starting at 1 (or 0 for none)
DynArea_QHandleArray      # 4*DynArea_NumQHandles   ;1 word per quick handle
                                                    ; - if free, word = index of next free quick handle (or 0 if none)
                                                    ; - if used, word = ptr to DANode (must be > DynArea_NumQHandles)
;
DynArea_ws_size           *  :INDEX:@               ;must be multiple of 4
;
            ASSERT DynArea_QHandleArray = DynArea_FreeQHandles +4
  ]
;


;        InsertDebugRoutines

; Exit from ChangeDynamicArea with error Not all moved

failure_IRQgoingClearSemaphore
        LDR     r0, =ZeroPage
      [ ZeroPage = 0
        STR     r0, [r0, #CDASemaphore]
      |
        MOV     r10, #0
        STR     r10, [r0, #CDASemaphore]
      ]
failure_IRQgoing
        ADR     r0, ErrorBlock_ChDynamNotAllMoved
ChangeDynamic_Error
        MOV     r10, #0
        STR     r0, [stack]
        LDR     lr, [stack, #4*10]
        ORR     lr, lr, #V_bit
        STR     lr, [stack, #4*10]
CDS_PostServiceWithRestore
      [ International
        LDR     r0, [stack]
        BL      TranslateError
        STR     r0, [stack]
      ]

; and drop thru to ...

CDS_PostService
        MOV     r1, #Service_MemoryMoved
        MOV     r0, r10                 ; amount moved
        MOVS    r2, r11                 ; which way was transfer?
        BMI     %FT47                   ; [definitely a grow]
        CMP     r11, #ChangeDyn_FreePool
        BNE     %FT48                   ; [definitely a shrink]
        CMP     r12, #ChangeDyn_AplSpace
        BEQ     %FT48                   ; [a shrink]
47
        RSB     r0, r0, #0             ; APLwork or free was source
        MOV     r2, r12                ; r2 = area indicator
48
        BL      Issue_Service

        MOV     r1, r10                ; amount moved

        Pull    "r0, r2-r9, r10, lr"
        ExitSWIHandler

        MakeErrorBlock ChDynamNotAllMoved


; in:   r0 = logical address where page is now

GetPageFlagsForR0IntoR6 Entry "R0-R2, R4-R5, R7"
;
; code from MoveCAMatR0toR3
;
        LDR     r5, =L2PT
        ADD     r4, r5, r0, LSR #10             ; r4 -> L2PT for log addr r0
        MOV     r2, r4, LSR #12
        LDR     r2, [r5, r2, LSL #2]            ; r2 = L2PT entry for r4
        TST     r2, #3                          ; if no page there
        BEQ     %FT90                           ; then cam corrupt

        LDR     r4, [r4]                        ; r4 = L2PT entry for r0
        TST     r4, #3                          ; check entry is valid too
        BEQ     %FT91
        MOV     r4, r4, LSR #12                 ; r4 = phys addr >> 12

        LDR     r2, =ZeroPage
        LDR     r6, [r2, #MaxCamEntry]
        ADD     r5, r2, #PhysRamTable
      [ ZeroPage <> 0
        MOV     r2, #0
      ]
10
        CMP     r2, r6                          ; if page we've got to is > max
        BHI     %FT92                           ; then corrupt
        LDMIA   r5!, {r7, lr}                   ; get phys.addr, size
        SUB     r7, r4, r7, LSR #12             ; number of pages into this bank
        CMP     r7, lr, LSR #12                 ; if too many
        ADDCS   r2, r2, lr, LSR #12             ; then advance physical page no.
        BCS     %BT10                           ; and loop

        ADD     r2, r2, r7                      ; add on number of pages within bank
;
; code from BangCamUpdate
;
        LDR     r1, =ZeroPage
        LDR     r1, [r1, #CamEntriesPointer]
        ADD     r1, r1, r2, LSL #3              ; point at cam entry (logaddr, PPL)
        LDMIA   r1, {r0, r6}                    ; r0 = current logaddress, r6 = current PPL
        EXIT

90
        ADR     lr, NoL2ForPageBeingRemovedError ; NB don't corrupt r0 yet - we need that in block as evidence
95
        STR     lr, [sp]                        ; update returned r0
        BL      StoreDebugRegs
        PullEnv                                 ; seriously broken memory
        SETV
        MOV     pc, lr

91
        ADR     lr, PageBeingRemovedNotPresentError
        B       %BT95

92
        ADR     lr, PhysicalAddressNotFoundError
        B       %BT95


; MoveCAMatR0toR3
; in:   r0 = old logaddr
;       r3 = new logaddr
;       r9 = MEMC CR
;       r11 = page protection level
;
; out:  r2 = physical page number of page moved, unless there was a serious error
;       r0,r1,r3,r6-r12 preserved
;       r4,r5 corrupted

MoveCAMatR0toR3 Entry "r0,r1,r6,r7"
        LDR     r5, =L2PT
        ADD     r4, r5, r0, LSR #10             ; r4 -> L2PT for log addr r0
        MOV     r2, r4, LSR #12
        LDR     r2, [r5, r2, LSL #2]            ; r2 = L2PT entry for r4
        TST     r2, #3                          ; if no page there
        BEQ     %FT90                           ; then cam corrupt

        LDR     r4, [r4]                        ; r4 = L2PT entry for r0
        TST     r4, #3                          ; check entry is valid too
        BEQ     %FT91
        MOV     r4, r4, LSR #12                 ; r4 = phys addr >> 12

        LDR     r2, =ZeroPage
        LDR     r6, [r2, #MaxCamEntry]
        ADD     r5, r2, #PhysRamTable
      [ ZeroPage <> 0
        MOV     r2, #0
      ]
10
        CMP     r2, r6                          ; if page we've got to is > max
        BHI     %FT92                           ; then corrupt
        LDMIA   r5!, {r7, lr}                   ; get phys.addr, size
        SUB     r7, r4, r7, LSR #12             ; number of pages into this bank
        CMP     r7, lr, LSR #12                 ; if too many
        ADDCS   r2, r2, lr, LSR #12             ; then advance physical page no.
        BCS     %BT10                           ; and loop

        ADD     r2, r2, r7                      ; add on number of pages within bank
        BL      BangCamUpdate
        CLRV
        EXIT

90
        ADR     lr, NoL2ForPageBeingRemovedError ; NB don't corrupt r0 yet - we need that in block as evidence
95
        STR     lr, [sp]                        ; update returned r0
        BL      StoreDebugRegs
        PullEnv                                 ; seriously broken memory
        SETV
        MOV     pc, lr

91
        ADR     lr, PageBeingRemovedNotPresentError
        B       %BT95

92
        ADR     lr, PhysicalAddressNotFoundError
        B       %BT95

StoreDebugRegs
        Push    "lr"
        MOV     lr, #CamMapCorruptDebugBlock
        STMIA   lr, {r0-lr}
        LDR     r0, [sp, #1*4]                  ; reload stacked r0 (error pointer)
        STR     r0, [lr, #15*4]                 ; store in stacked PC position
        Pull    "pc"

NoL2ForPageBeingRemovedError
        &       0
        =       "Memory Corrupt: No L2PT for page being removed", 0
        ALIGN

PageBeingRemovedNotPresentError
        &       0
        =       "Memory Corrupt: Page being removed was not present", 0
        ALIGN

PhysicalAddressNotFoundError
        &       0
        =       "Memory Corrupt: Physical address not found", 0
        ALIGN

CamMapBroke
        &       0
        =       "!!!! CAM Map Corrupt !!!!", 0
        ALIGN
Call_CAM_Mapping
        Push    "r0, r1, r4, r6, lr"
        BL      BangCamUpdate
        Pull    "r0, r1, r4, r6, pc"


; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 bits 0..6 = area number
;       r0 bit 7 set => return max area size in r2 (implemented 13 Jun 1990)
;                       this will return an error if not implemented
; Out   r0 = address of area
;       r1 = current size of area
;       r2 = max size of area if r0 bit 7 set on entry (preserved otherwise)

; TMD 19-May-93: When this is updated to new CDA list, change meaning as follows:

; r0 in range 0..&7F    return address, size of area r0
;             &80..&FF  return address, size, maxsize of area (r0-&80)
;             &100..    return address, size, maxsize of area r0

; TMD 20-Aug-93: New bit added - if r0 = -1 on entry, then returns info on application space
; r0 = base address (&8000)
; r1 = current size (for current task)
; r2 = maximum size (eg 16M-&8000)

ReadDynamicArea ROUT

readdyn_returnR2bit     *       &80
        ASSERT  ChangeDyn_MaxArea < readdyn_returnR2bit

        CMP     r0, #-1                         ; if finding out about app space
      [ ZeroPage = 0
        LDREQ   r1, [r0, #AplWorkSize+1]        ; then r1 = current size
      |
        LDREQ   r1, =ZeroPage
        LDREQ   r1, [r1, #AplWorkSize]
      ]
; [ HAL32
;        LDREQ   r2, [r0, #RAMLIMIT+1]
; |
        LDREQ   r2, =AplWorkMaxSize             ; and r2 = max size
; ]
        MOVEQ   r0, #&8000                      ; r0 = base address
        SUBEQ   r1, r1, r0                      ; adjust size and maxsize
        SUBEQ   r2, r2, r0                      ; to remove bottom 32K
        ExitSWIHandler EQ

; first check if it's one of the new ones

        Push    "r1,lr"
        CMP     r0, #&100                       ; if area >= &100
        MOVCS   r1, r0                          ; then just use area
        BICCC   r1, r0, #readdyn_returnR2bit    ; else knock off bit 7
  [ DynArea_QuickHandles
        BL      QCheckAreaNumber                ; out: r10 -> node
  |
        BL      CheckAreaNumber                 ; out: r10 -> node
  ]
        Pull    "r1,lr"
        BCC     %FT05                           ; [not a new one, so use old code]

        CMP     r0, #&80                        ; CS => load maxsize into R2
                                                ; (do this either if bit 7 set, or area >=&100)
        LDRCS   r2, [r10, #DANode_MaxSize]
        LDR     r11, [r10, #DANode_Flags]       ; if doubly mapped
        LDR     r1, [r10, #DANode_Size]         ; r1 = current size
        LDR     r0, [r10, #DANode_Base]         ; r0 -> base
        TST     r11, #DynAreaFlags_DoublyMapped
        SUBNE   r0, r0, r1                      ; then return start of 1st copy for compatibility
        ExitSWIHandler
05
        ADRL    r0, ErrorBlock_BadDynamicArea
      [ International
        Push    "lr"
        BL      TranslateError
        Pull    "lr"
      ]
        B       SLVK_SetV

        MakeErrorBlock  BadDynamicArea

; *************************************************************************
; User access to CAM mapping
; ReadMemMapInfo:
; returns R0 = pagsize
;         R1 = number of pages in use  (= R2 returned from SetEnv/Pagesize)
; *************************************************************************

ReadMemMapInfo_Code
      LDR      R10, =ZeroPage
      LDR      R0, [R10, #Page_Size]
      LDR      R1, [R10, #RAMLIMIT]    ; = total memory size
      ADRL     R11, PageShifts-1
      LDRB     R11, [R11, R0, LSR #12]
      MOV      R1, R1, LSR R11
      ExitSWIHandler

; ************************************************************************
; SWI ReadMemMapEntries: R0 pointer to list.
;  Entries are three words long, the first of which is the CAM page number.
;  List terminated by -1.
; Returns pagenumber (unaltered)/address/PPL triads as below
; ************************************************************************

ReadMemMapEntries_Code  ROUT
        Push    "r0,r14"
        LDR     r14, =ZeroPage
        LDR     r10, [r14, #CamEntriesPointer]
        LDR     r14, [r14, #MaxCamEntry]
01
        LDR     r12, [r0], #4                   ; page number
        CMP     r12, r14
        Pull    "r0,r14", HI
        ExitSWIHandler HI

   [ ChocolateAMB
        ;may need AMB to make mapping honest (as if not lazy), if page is in currently mapped app
        Push    "r0, lr"
        MOV     r0, r12                         ; page number to make honest
        BL      AMB_MakeHonestPN
        Pull    "r0, lr"
   ]

        ADD     r11, r10, r12, LSL #3
        LDMIA   r11, {r11, r12}
        STMIA   r0!, {r11, r12}
        B       %BT01

; ************************************************************************
; SWI FindMemMapEntries:
; In:  R0 -> table of 12-byte page entries
;       +0      4       probable page number (0..npages-1) (use 0 if no idea)
;       +4      4       logical address to match with
;       +8      4       undefined
;       terminated by a single word containing -1
;
; Out: table of 12-byte entries updated:
;       +0      4       actual page number (-1 => not found)
;       +4      4       address (preserved)
;       +8      4       page protection level (3 if not found)
;       terminator preserved
;
; ************************************************************************

FindMemMapEntries_Code  ROUT

; Code for expanded CAM map version

        Push    "r0, r9, r14"
        LDR     r14, =ZeroPage
        LDR     r9, [r14, #MaxCamEntry]
        LDR     r14, [r14, #CamEntriesPointer]  ; r14 -> start of cam map
        ADD     r9, r14, r9, LSL #3             ; r9 -> first word of last entry in cam map
10
        LDR     r10, [r0, #0]                   ; r10 = guess page number (or -1)
        CMP     r10, #-1                        ; if -1 then end of list
        Pull    "r0, r9, r14", EQ               ; so restore registers
        ExitSWIHandler EQ                       ; and exit

        LDR     r11, [r0, #4]                   ; r11 = logical address

   [ ChocolateAMB
        ;may need AMB to make mapping honest (as if not lazy), if page is in currently mapped app
        Push    "r0, lr"
        MOV     r0, r11                         ; logical address to make honest
        BL      AMB_MakeHonestLA                ; note, quickly dismisses non app space addresses
        Pull    "r0, lr"
   ]

        ADD     r10, r14, r10, LSL #3           ; form address with 'guess' page
        CMP     r10, r9                         ; if off end of CAM
        BHI     %FT20                           ; then don't try to use the guess

        LDR     r12, [r10]                      ; load address from guessed page
        TEQ     r11, r12                        ; compare address
        BEQ     %FT60                           ; if equal, then guessed page was OK
20

; for now, cheat by looking in L2PT, to see if we can speed things up

        Push    "r5-r8"                         ; need some registers here!
        LDR     r10, =L2PT
        MOV     r8, r11, LSR #12                ; r8 = logical page number
        ADD     r8, r10, r8, LSL #2             ; r8 -> L2PT entry for log.addr
        MOV     r5, r8, LSR #12                 ; r5 = page offset to L2PT entry for log.addr
        LDR     r5, [r10, r5, LSL #2]           ; r5 = L2PT entry for L2PT entry for log.addr
        TST     r5, #3                          ; if page not there
        SUBEQ   r10, r9, #8                     ; then invalid page so go from last one
        BEQ     %FT45
        LDR     r8, [r8]                        ; r8 = L2PT entry for log.addr
        MOV     r8, r8, LSR #12                 ; r8 = physaddr / 4K

        LDR     r5, =ZeroPage+PhysRamTable
        SUB     r10, r14, #8
30
        CMP     r10, r9                         ; have we run out of RAM banks?
        BCS     %FT40                           ; then fail
        LDMIA   r5!, {r6,r7}                    ; load next address, size
        SUB     r6, r8, r6, LSR #12             ; number of pages into this bank
        CMP     r6, r7, LSR #12                 ; if more than there are
        ADDCS   r10, r10, r7, LSR #12-3         ; then advance CAM entry position
        BCS     %BT30                           ; and loop to next bank

        ADD     r10, r10, r6, LSL #3            ; advance by 2 words for each page in this bank
40
        SUBCS   r10, r9, #8                     ; search from last one, to fail quickly (if CS)
45
        Pull    "r5-r8"
50
        CMP     r10, r9                         ; if not just done last one,
        LDRNE   r12, [r10, #8]!                 ; then get logical address
        TEQNE   r11, r12                        ; compare address
        BNE     %BT50                           ; loop if not same and not at end

; either found page or run out of pages

        TEQ     r11, r12                        ; see if last one matched
                                                ; (we always load at least one!)
60
        LDREQ   r12, [r10, #4]                  ; if match, then r12 = PPL
        SUBEQ   r10, r10, r14                   ; and page number=(r10-r14)>>3
        MOVEQ   r10, r10, LSR #3

        MOVNE   r10, #-1                        ; else unknown page number indicator
        MOVNE   r12, #3                         ; and PPL=3 (no user access)

        STMIA   r0!, {r10-r12}                  ; store all 3 words
        B       %BT10                           ; and go back for another one

;**************************************************************************
; SWI SetMemMapEntries: R0 pointer to list of CAM page/address/PPL triads,
;  terminated by -1.
; address of -1 means "put the page out of the way"
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

; note, if ChocolateAMB, no MakeHonest consideration here, this SWI just
; changes the mapping of pages regardless of their current mapping, and
; assumes the caller knows what he is doing (ho ho)
;

SetMemMapEntries_Code  ROUT
        Push    "r0-r6, r9, lr"
        MOV     r12, r0

; BangCamUpdate takes entry no in r2, logaddr to set to in r3
; r9 current MEMC, r11 = PPL
; corrupts r0,r1,r4,r6

        LDR     r9, =ZeroPage
        LDR     r5, [r9, #MaxCamEntry]
        LDR     r9, [r9, #MEMC_CR_SoftCopy]
01
        LDR     r2, [r12], #4
        CMP     r2, r5
        BHI     %FT02                   ; finished
        LDMIA   r12!, {r3, r11}
 [ {FALSE}
        AND     r11, r11, #3
 ]
        CMP     r3, #-1
        LDRHS   r3, =DuffEntry
        MOVHS   r11, #AP_Duff
        BL      BangCamUpdate
        B       %BT01
02
        Pull    "r0-r6, r9, lr"
        ExitSWIHandler

        LTORG



;**************************************************************************
;
;       DynamicAreaSWI - Code to handle SWI OS_DynamicArea
;
; in:   r0 = reason code
;       Other registers depend on reason code
;
; out:  Depends on reason code
;


DAReason_Create          *  0
DAReason_Remove          *  1
DAReason_GetInfo         *  2
DAReason_Enumerate       *  3
DAReason_Renumber        *  4
 [ ShrinkableDAs
DAReason_ReturnFree      *  5   ;if ShrinkableDAs, else reason code invalid
 ]
 [ DynArea_QuickHandles
DAReason_GetChangeInfo   *  6   ;if DynArea_Quickhandles, else reason code invalid
DAReason_EnumerateInfo   *  7   ;if DynArea_Quickhandles, else reason code invalid
DAReason_SetClamps       *  8   ;if DynArea_Quickhandles, else reason code invalid
 ]
 [ DA_Batman
DAReason_SparseClaim     *  9   ;if DA_Batman, else reason code invalid
DAReason_SparseRelease   * 10   ;if DA_Batman, else reason code invalid
 ]
DAReason_Limit           * 11   ;end of defined DA reasons


DynArea_NewAreas *      &100            ; Allocated area numbers start here
DynArea_NewAreasBase *  &04000000       ; Allocated area addresses start here


; Bits in dynamic area flags (and page flags)
;
; rearranged slightly for Ursula - allow room for some expansion of DA flags in future without moving page flags
; mjs June 2001 merged the Ursula arrangement into HALised kernel
;
DynAreaFlags_APBits             * 15 :SHL:  0   ; currently only uses 2 bits, but may extend to allow svc/usr read-only
DynAreaFlags_NotBufferable      *  1 :SHL:  4
DynAreaFlags_NotCacheable       *  1 :SHL:  5
DynAreaFlags_DoublyMapped       *  1 :SHL:  6
DynAreaFlags_NotUserDraggable   *  1 :SHL:  7
DynAreaFlags_NeedsSpecificPages *  1 :SHL:  8   ; whether area will ever require particular physical pages
DynAreaFlags_Shrinkable         *  1 :SHL:  9   ; whether area may be shrunk when need more space in free pool
DynAreaFlags_SparseMap          *  1 :SHL: 10   ; whether area may have non-contiguous mapping of pages (Holey dynamic areas Batman!)
DynAreaFlags_PiersBinding       *  1 :SHL: 11   ; whether area is bound to client application, and so may be swapped out with it (not implemented yet)
DynAreaFlags_CPBits             *  7 :SHL: 12   ; cache policy variant for NotBufferable and NotCacheable bits

; Cache policies
;
CP_NCNB_Default                 * 0             ; no policy variants

CP_NCB_Default                  * 0             ; OS decides buffer policy (currently always coalescing)
CP_NCB_NonMerging               * 1             ; Non-merging write buffer. If not available, unbuffered.
CP_NCB_Merging                  * 2             ; Merging write buffer. If not available, non-merging.

CP_CNB_Default                  * 0             ; OS decides cache policy (writethrough). NCNB if not available
CP_CNB_Writethrough             * 1             ; Writethrough cacheable, non-buffered. If not available, NCNB.
CP_CNB_Writeback                * 2             ; Writeback cacheable, non-buffered. If not available,CNB_Writethrough.

CP_CB_Default                   * 0             ; OS decides cache policy (WB if available, W alloc if HAL requests)
CP_CB_Writethrough              * 1             ; Writethrough cacheable, read allocate. If not available, NCB_Merging
CP_CB_WritebackReadAlloc        * 2             ; Writeback cacheable, read allocate. If not available, writethrough.
CP_CB_WritebackWriteAlloc       * 3             ; Writeback cacheable, write allocate. If not available, WB/R.
CP_CB_AlternativeDCache         * 4             ; Use XScale/SA11x0 mini-data cache. If not available, CB_Default.

;
; bits 15 nominally reserved for future DA flags expansion
;
DynAreaFlags_AccessMask * DynAreaFlags_APBits :OR: DynAreaFlags_NotBufferable :OR: DynAreaFlags_NotCacheable :OR: DynAreaFlags_DoublyMapped :OR: DynAreaFlags_CPBits
;
; The following bits are only present in page flags
;
TempUncacheableShift            * 16
PageFlags_TempUncacheableBits   * 15 :SHL: TempUncacheableShift    ; temporary count of uncacheability, used by DMA mgr
PageFlags_Unavailable           *  1 :SHL: 20                      ; physical page may not be requested by a PreShrink handler
;
; Temporary flags only used by kernel
;
PageFlags_Required              *  1 :SHL: 21                      ; physical page asked for by handler


DynamicAreaSWI Entry
        BL      DynAreaSub
        PullEnv
        ORRVS   lr, lr, #V_bit
        ExitSWIHandler

DynAreaSub
        CMP     r0, #DAReason_Limit
        ADDCC   pc, pc, r0, LSL #2
        B       DynArea_Unknown
        B       DynArea_Create
        B       DynArea_Remove
        B       DynArea_GetInfo
        B       DynArea_Enumerate
        B       DynArea_Renumber
 [ ShrinkableDAs
        B       DynArea_ReturnFree
 |
        B       DynArea_Unknown
 ]
 [ DynArea_QuickHandles
        B       DynArea_GetChangeInfo
        B       DynArea_EnumerateInfo
        B       DynArea_SetClamps
 |
        B       DynArea_Unknown
        B       DynArea_Unknown
        B       DynArea_Unknown
 ]
 [ DA_Batman
        B       DynArea_SparseClaim
        B       DynArea_SparseRelease
 |
        B       DynArea_Unknown
        B       DynArea_Unknown
 ]
;
; unknown OS_DynamicArea reason code
;
DynArea_Unknown
        ADRL    r0, ErrorBlock_HeapBadReason
DynArea_TranslateAndReturnError
      [ International
        Push    lr
        BL      TranslateError
        Pull    lr
      ]
DynArea_ReturnError
        SETV
        MOV     pc, lr

;**************************************************************************
;
;       DynArea_Create - Create a dynamic area
;
;       Internal routine called by DynamicAreaSWI and by reset code
;
; in:   r0 = reason code (0)
;       r1 = new area number, or -1 => RISC OS allocates number
;       r2 = initial size of area (in bytes)
;       r3 = base logical address of area, or -1 => RISC OS allocates address space
;       r4 = area flags
;               bits 0..3 = access privileges
;               bit  4 = 1 => not bufferable
;               bit  5 = 1 => not cacheable
;               bit  6 = 0 => area is singly mapped
;                      = 1 => area is doubly mapped
;               bit  7 = 1 => area is not user draggable in TaskManager window
;               bit  8 = 1 => area may require specific physical pages
;               bit  9 = 1 => area is shrinkable (only implemented if ShrinkableDAs)
;               bit 10 = 1 => area may be sparsely mapped (only implemented if DA_Batman)
;               bit 11 = 1 => area is bound to client application (allows areas to be overlayed in address space)
;                             not implemented yet, but declared in public API for Ursula
;               bits 12..15 reserved for future DA flag expansion (should be 0)
;               bits 16..31 reserved for internal page flags (should be 0)
;
;       r5 = maximum size of area, or -1 for total RAM size
;       r6 -> area handler routine
;       r7 = workspace pointer for area handler (-1 => use base address)
;       r8 -> area description string (null terminated) (gets copied)
;
; out:  r1 = given or allocated area number
;       r3 = given or allocated base address of area
;       r5 = given or allocated maximum size
;       r0, r2, r4, r6-r9 preserved
;       r10-r12 may be corrupted
;

DynArea_Create Entry "r2,r6-r8"
        CMP     r1, #-1         ; do we have to allocate a new area number
        BEQ     %FT10

  [ DynArea_QuickHandles
        CMP     r1, #DynArea_NewAreas
        BLO     %FT06
        CMP     r1, #DynArea_NewAreas+DynArea_NumQHandles
        BLO     %FT08           ; can't choose your own quick handle
  ]

06
  [ DynArea_QuickHandles
        BL      QCheckAreaNumber ; see if area number is unique
  |
        BL      CheckAreaNumber ; see if area number is unique
  ]
        BCC     %FT20           ; didn't find it, so OK

08
        ADR     r0, ErrorBlock_AreaAlreadyExists
DynArea_ErrorTranslateAndExit
        PullEnv
        B       DynArea_TranslateAndReturnError

        MakeErrorBlock  AreaAlreadyExists
        MakeErrorBlock  AreaNotOnPageBdy
        MakeErrorBlock  OverlappingAreas
        MakeErrorBlock  CantAllocateArea
        MakeErrorBlock  CantAllocateLevel2
        MakeErrorBlock  UnknownAreaHandler

; we have to allocate an area number for him

  [ DynArea_QuickHandles
10
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws ]
        LDR     r1, DynArea_FreeQHandles          ;get index of next available quick handle, if any free
        CMP     r1, #0
        ADDNE   r1, r1, #DynArea_NewAreas-1       ;compute quick handle from index
        BNE     %FT20
        LDR     r1, DynArea_TreacleGuess          ;last non-quick number allocated
12
        ADD     r1, r1, #1                        ; increment for next guess (collisions should be *very* rare)
        CMP     r1, #DynArea_NewAreas+DynArea_NumQHandles
        MOVLO   r1, #DynArea_NewAreas+DynArea_NumQHandles
        BL      QCheckAreaNumber
        BCS     %BT12                             ; and try again
  |
10
        MOV     r1, #DynArea_NewAreas
12
        BL      CheckAreaNumber
        ADDCS   r1, r1, #1      ; that area number already exists, so increment
        BCS     %BT12           ; and try again
  ]

20

; now validate maximum size of area

  [ DynArea_QuickHandles
    ;
    ; apply clamps on max size, as set by last call to OS_DynamicArea 8
    ;
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
    [ DA_Batman
        TST     r4, #DynAreaFlags_SparseMap
        BEQ     DAC_notsparse
        LDR     r10, DynArea_OD8Clamp3   ; clamp for sparse dynamic area
        CMP     r5, r10                  ; if requested max size is > relevant clamp
        MOVHI   r5, r10                  ; then clamp it!
        LDR     r10, [sp]                ; requested initial size, from stack
        CMP     r5, r10
        MOVLO   r5, r10                  ; we must try to honour initial size (allowed to break clamp)
        LDR     r10, =ZeroPage
        LDR     r11, [r10, #Page_Size]
        B       DAC_roundup
DAC_notsparse
    ]
        CMP     r5, #-1
        LDREQ   r10, DynArea_OD8Clamp1   ; clamp for max size requested of -1
        LDRNE   r10, DynArea_OD8Clamp2   ; clamp for max size requested of some specific value
        CMP     r5, r10                  ; if requested max size is > relevant clamp
        MOVHI   r5, r10                  ; then clamp it!
        LDR     r10, [sp]                ; requested initial size, from stack
        CMP     r5, r10
        MOVLO   r5, r10                  ; we must try to honour initial size (allowed to break clamp)
  ]

        LDR     r10, =ZeroPage
        LDR     r11, [r10, #Page_Size]
        LDR     r10, [r10, #RAMLIMIT]   ; get total RAM size
        CMP     r5, r10                 ; if requested maximum size is > total
        MOVHI   r5, r10                 ; then set max to total (NB. -1 passed in always yields HI)

DAC_roundup
        SUB     r10, r11, #1            ; also round up to a page multiple
        ADD     r5, r5, r10
        BIC     r5, r5, r10

; now see if we have to allocate a logical address space

        CMP     r3, #-1                 ; if we are to allocate the address space
        BEQ     %FT30                   ; then go do it

; otherwise we must check that the address does not clash with anything else

        TST     r3, r10                         ; does it start on a page boundary
        ADRNE   r0, ErrorBlock_AreaNotOnPageBdy ; if not then error
        BNE     DynArea_ErrorTranslateAndExit

        BL      CheckForOverlappingAreas        ; in: r3 = address, r4 = flags, r5 = size; out: if error, r0->error, V=1
        BVC     %FT40
25
        PullEnv
        B       DynArea_ReturnError

30
        BL      AllocateAreaAddress             ; in: r4 = flags, r5 = size of area needed; out: r3, or V=1, r0->error
        BVS     %BT25
40
        BL      AllocateBackingLevel2           ; in: r3 = address, r4 = flags, r5 = size; out: VS if error
        BVS     %BT25

        Push    "r0,r1,r3"
  [ DynArea_QuickHandles
    ;we save work and reduce stress on system heap by claiming only one block, consisting of node followed by
    ;string space (always maximum length, but typically not overly wasteful compared to 2nd block overhead)
    ;
        MOV     r3, #DANode_NodeSize + DynArea_MaxNameLength + 1
  |
        MOV     r3, #DANode_NodeSize
  ]
        BL      ClaimSysHeapNode                ; out: r2 -> node
        STRVS   r0, [sp]
        Pull    "r0,r1,r3"
        BVS     %BT25                           ; failed to claim node

; now store data in node (could probably use STM if we shuffled things around)

        CMP     r7, #-1                         ; if workspace ptr = -1
        MOVEQ   r7, r3                          ; then use base address

        STR     r1, [r2, #DANode_Number]
        STR     r3, [r2, #DANode_Base]
  [ DA_Batman
        ;disallow some awkward flag options if SparseMap set (no error), and temporarily create as not sparse
        ;also disallow a DA handler
        TST     r4, #DynAreaFlags_SparseMap
        STREQ   r4, [r2, #DANode_Flags]
        BICNE   r7, r4, #DynAreaFlags_DoublyMapped + DynAreaFlags_NeedsSpecificPages + DynAreaFlags_Shrinkable + DynAreaFlags_SparseMap
        ORRNE   r7, r7, #DynAreaFlags_NotUserDraggable
        STRNE   r7, [r2, #DANode_Flags]
        MOVNE   r6, #0
        MOVNE   r7, #0
  |
        STR     r4, [r2, #DANode_Flags]
  ]
        STR     r5, [r2, #DANode_MaxSize]
        STR     r6, [r2, #DANode_Handler]
        STR     r7, [r2, #DANode_Workspace]
        MOV     r7, #0                          ; initial size is zero
        STR     r7, [r2, #DANode_Size]          ; before we grow it

  [ HAL
        ; update lower limit on IO space growth, if this DA exceeds previous limit
      [ ZeroPage <> 0
        LDR     r7, =ZeroPage
      ]
        LDR     r6, [r7, #IOAllocLimit]
        ADD     lr, r3, r5
        CMP     lr, r6
        STRHI   lr, [r7, #IOAllocLimit]
  ]

; now make copy of string - first find out length of string

  [ DynArea_QuickHandles

        ADD     r7, r2, #DANode_NodeSize
        STR     r7, [r2, #DANode_Title]
        Push    "r0"
        MOV     r0, #DynArea_MaxNameLength
        TEQ     r8, #0
    [ DynArea_NullNamePtrMeansHexString
        ASSERT  DynArea_MaxNameLength > 8
        BNE     %FT45
        Push    "r1, r2"
        MOV     r0, r1                          ;string is 8-digit hex of DA number
        MOV     r1, r7
        MOV     r2, #DynArea_MaxNameLength+1
        SWI     XOS_ConvertHex8
        Pull    "r1, r2"
        B       %FT55
    |
        BEQ     %FT50                           ;assume NULL ptr to mean no DA name
    ]
45
        LDRB    r6, [r8], #1
        STRB    r6, [r7], #1
        SUB     r0, r0, #1
        TEQ     r6, #0
        TEQNE   r0, #0
        BNE     %BT45
50
        MOV     r0, #0
        STRB    r0, [r7], #1
55
        Pull    "r0"

  |

        MOV     r7, r8
45
        LDRB    r6, [r7], #1
        TEQ     r6, #0
        BNE     %BT45

        Push    "r0-r3"
        SUB     r3, r7, r8                      ; r3 = length inc. term.
        BL      ClaimSysHeapNode
        STRVS   r0, [sp]
        MOV     r7, r2
        Pull    "r0-r3"
        BVS     StringNodeClaimFailed

        STR     r7, [r2, #DANode_Title]
50
        LDRB    r6, [r8], #1                    ; copy string into claimed block
        STRB    r6, [r7], #1
        TEQ     r6, #0
        BNE     %BT50

 ] ;DynArea_QuickHandles

; now put node on list - list is sorted in ascending base address order

        LDR     r8, =ZeroPage+DAList
        LDR     r6, [r2, #DANode_Base]
60
        MOV     r7, r8
        ASSERT  DANode_Link = 0
        LDR     r8, [r7, #DANode_Link]          ; get next node
        TEQ     r8, #0                          ; if no more
        BEQ     %FT70                           ; then put it on here
        LDR     lr, [r8, #DANode_Base]
        CMP     lr, r6                          ; if this one is before ours
        BCC     %BT60                           ; then loop

70
        STR     r8, [r2, #DANode_Link]
        STR     r2, [r7, #DANode_Link]

  [ DynArea_QuickHandles
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        ;so XOS_ChangeDynamicArea can pick up the node we are still creating
        STR     r1, DynArea_CreatingHandle
        STR     r2, DynArea_CreatingPtr
        ;so initial grow won't leave resize signature
        LDR     lr, DynArea_OD6Signature
        ORR     lr, lr, #&80000000
        STR     lr, DynArea_OD6Signature
  ]

; now we need to grow the area to its requested size

        Push    "r0, r1, r2"
        LDR     r0, [r2, #DANode_Number]
        LDR     r1, [sp, #3*4]                  ; reload requested size off stack
        SWI     XOS_ChangeDynamicArea           ; deal with error - r0,r1,r2 still stacked
        BVS     %FT90
        Pull    "r0, r1, r2"

  [ DynArea_QuickHandles
;
; Now put node on alphabetically sorted list
;
        Push    "r3,r4,r5,r7,r8,r9"
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        ADR     r8, DynArea_SortedList - DANode_SortLink ;so that [r8, #DANode_SortLink] addresses list header)
75
        MOV     r7, r8                       ; previous
        LDR     r8, [r7, #DANode_SortLink]
        TEQ     r8, #0
        BEQ     %FT78
        ;ho hum, UK case insensitive string compare
        LDR     r3, [r2, #DANode_Title]
        LDR     r9, [r8, #DANode_Title]
76
        LDRB    r4, [r3],#1
        uk_LowerCase r4,r11
        LDRB    r5, [r9],#1
        uk_LowerCase r5,r11
        CMP     r4, r5
        BNE     %FT77
        CMP     r4, #0
        BNE     %BT76
77
        BHI     %BT75
78
        STR     r2, [r7, #DANode_SortLink]
        STR     r8, [r2, #DANode_SortLink]
79
        Pull    "r3,r4,r5,r7,r8,r9"
  ] ;DynArea_QuickHandles

  [ DA_Batman
        TST     r4, #DynAreaFlags_SparseMap
        LDRNE   r11, [r2, #DANode_Flags]
        ORRNE   r11, r11, #DynAreaFlags_SparseMap ; set this in node now (after initial grow)
        STRNE   r11, [r2, #DANode_Flags]
        LDRNE   r11, [r2, #DANode_Size]
        LDRNE   lr,  [r2, #DANode_Base]
        ADDNE   r11, r11, lr
        STRNE   r11, [r2, #DANode_SparseHWM]      ; initial high water mark
  ]

  [ DynArea_QuickHandles
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]

        TST     r4, #DynAreaFlags_Shrinkable
        LDRNE   lr, DynArea_ShrinkableSubList
        STRNE   lr, [r2, #DANode_SubLink]
        STRNE   r2, DynArea_ShrinkableSubList   ;link onto front of Shrinkable sublist if Shrinkable

        MOV     lr, #-1
        STR     lr, DynArea_CreatingHandle      ;invalidate this now
        CMP     r1, #ChangeDyn_MaxArea
        BHI     %FT72
        ADR     lr, DynArea_SysQHandleArray
        STR     r2, [lr, r1, LSL #2]            ;system handle - store ptr to node for quick reference
        B       %FT80
72
        LDR     lr, DynArea_OD6Signature
        STR     lr, DynArea_OD6PrevSignature
        ORR     lr, lr, #1
        STR     lr, DynArea_OD6Signature
        STR     r1, DynArea_OD6Handle
        CMP     r1, #DynArea_NewAreas
        BLO     %FT80
        CMP     r1, #DynArea_NewAreas+DynArea_NumQHandles
        BHS     %FT74
        SUB     r10, r1, #DynArea_NewAreas
        ADR     lr, DynArea_QHandleArray
        LDR     r6, [lr, r10, LSL #2]           ;pick up index of next free quick handle
        STR     r6, DynArea_FreeQHandles        ;store as index of first free quick handle
        STR     r2, [lr, r10, LSL #2]           ;store ptr to node for quick reference
        B       %FT80
74
        LDR     r10, DynArea_TreacleGuess
        ADD     r10, r10, #1
        STR     r10, DynArea_TreacleGuess       ;non-quick handle allocated, increment for next allocate
80
  ] ;DynArea_QuickHandles


; Now issue service to tell TaskManager about it

        Push    "r0, r1, r2"
        MOV     r2, r1                          ; r2 = area number
        MOV     r1, #Service_DynamicAreaCreate
        BL      Issue_Service
        Pull    "r0, r1, r2"

        CLRV
        EXIT

90

; The dynamic area is not being created, because we failed to grow the area to the required size.
; The area itself will have no memory allocated to it (since if grow fails it doesn't move any).
; We must delink the node from our list, free the string node, and then the area node itself.

        STR     r0, [sp, #0*4]                  ; remember error pointer in stacked r0
        STR     r8, [r7, #DANode_Link]          ; delink area
  [ :LNOT: DynArea_QuickHandles
        LDR     r2, [r2, #DANode_Title]
        BL      FreeSysHeapNode                 ; free title string node
  ]
        Pull    "r0, r1, r2"                    ; pull stacked registers, and drop thru to...

  [ :LNOT: DynArea_QuickHandles
; The dynamic area is not being created, because there is no room to allocate space for the title string
; We must free the DANode we have allocated
; It would be nice to also free the backing L2, but we'll leave that for now.

; in: r2 -> DANode

StringNodeClaimFailed
  ]

        Push    "r0, r1"
        BL      FreeSysHeapNode
        Pull    "r0, r1"
  [ DynArea_QuickHandles
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        MOV     lr, #-1
        STR     lr, DynArea_CreatingHandle
  ]
        PullEnv
        B       DynArea_ReturnError

        LTORG

;**************************************************************************
;
;       DynArea_Remove - Remove a dynamic area
;
;       Internal routine called by DynamicAreaSWI
;
; in:   r0 = reason code (1)
;       r1 = area number
;
; out:  r10-r12 may be corrupted
;       All other registers preserved
;

DynArea_Remove Entry

        ;*MUST NOT USE QCheckAreaNumber* (need r11 = previous)
        BL      CheckAreaNumber         ; check that area is there
        BCC     UnknownDyn              ; [not found]

  [ DA_Batman
        Push    "r11"
        LDR     r11,[r10,#DANode_Flags]
        TST     r11,#DynAreaFlags_SparseMap
        Pull    "r11"
        BEQ     DAR_notsparse
        Push    "r0,r2-r3"
        MOV     r0,#DAReason_SparseRelease
        LDR     r2,[r10,#DANode_Base]
        LDR     r3,[r10,#DANode_MaxSize]
        SWI     XOS_DynamicArea            ;release all pages in sparse area
        STRVS   r0,[SP]
        Pull    "r0,r2-r3"
        EXIT    VS
        B       DAR_delink
DAR_notsparse
  ]

  [ DynArea_QuickHandles
        Push    "r11"
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        ;so final shrink won't leave resize signature
        LDR     lr, DynArea_OD6Signature
        ORR     lr, lr, #&80000000
        STR     lr, DynArea_OD6Signature
        Pull    "r11"
  ]

; First try to shrink area to zero size

        Push    "r0-r2"
        MOV     r0, r1                  ; area number
        LDR     r2, [r10, #DANode_Size] ; get current size
        RSB     r1, r2, #0              ; negate it
        SWI     XOS_ChangeDynamicArea
        BVS     %FT80
        Pull    "r0-r2"

DAR_delink

  [ DynArea_QuickHandles
;
; delink from sorted list
;
        Push    "r7,r8,r11"
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        ADR     r8, DynArea_SortedList - DANode_SortLink ;so that [r8, #DANode_SortLink] addresses list header)
DAR_sdloop
        MOV     r7, r8                      ;previous
        LDR     r8, [r7, #DANode_SortLink]
        TEQ     r8, #0                      ;just in case not on list, shouldn't happen
        BEQ     DAR_sddone
        TEQ     r8, r10
        BNE     DAR_sdloop
        LDR     r8, [r8, #DANode_SortLink]
        STR     r8, [r7, #DANode_SortLink]
DAR_sddone
        Pull    "r7,r8,r11"

  ] ;DynArea_QuickHandles

; Now just de-link from list (r10 -> node, r11 -> prev)

        LDR     lr, [r10, #DANode_Link] ; store our link
        STR     lr, [r11, #DANode_Link] ; in prev link

  [ DynArea_QuickHandles
        ;if it is a Shrinkable area, find on Shrinkable sublist and remove it
        Push    "r0-r2, r11"
        LDR     r0, [r10, #DANode_Flags]
        TST     r0, #DynAreaFlags_Shrinkable
        BEQ     %FT06
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        ADR     r1, DynArea_ShrinkableSubList
        LDR     r2, [r1]
04
        CMP     r2, r10
        LDREQ   r2, [r2, #DANode_SubLink]
        STREQ   r2, [r1]
        BEQ     %FT06
        ADD     r1, r2, #DANode_SubLink
        LDR     r2, [r2, #DANode_SubLink]
        B       %BT04
06
        Pull    "r0-r2, r11"
  ]

        Push    "r0-r2"
  [ :LNOT: DynArea_QuickHandles
        LDR     r2, [r10, #DANode_Title]        ; free title string block
        BL      FreeSysHeapNode
  ]
        MOV     r2, r10                         ; and free node block
        BL      FreeSysHeapNode
        Pull    "r0-r2"

  [ DynArea_QuickHandles
        Push    "r2, r3"
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        MOV     r2,#-1
        STR     r2, DynArea_CreatingHandle                ; invalidate these, just in case
        STR     r2, DynArea_LastTreacleHandle
        STR     r2, DynArea_LastEnumHandle
        CMP     r1, #ChangeDyn_MaxArea
        BLS     %FT08                                     ; system area being removed
        LDR     r2, DynArea_OD6Signature
        STR     r2, DynArea_OD6PrevSignature
        ORR     r2, r2, #2
        STR     r2, DynArea_OD6Signature
        STR     r1, DynArea_OD6Handle
        CMP     r1, #DynArea_NewAreas
        BLO     %FT10
        CMP     r1, #DynArea_NewAreas+DynArea_NumQHandles
        BHS     %FT10
        SUB     r2, r1, #DynArea_NewAreas-1               ; index of quick handle
        ADR     r10, DynArea_FreeQHandles                 ; so we can index array from 1
        LDR     r3, DynArea_FreeQHandles
        STR     r3, [r10, r2, LSL #2]
        STR     r2, DynArea_FreeQHandles                  ; put on front of free list
        B       %FT10
08
        ADR     r10, DynArea_SysQHandleArray
        MOV     r2,  #0
        STR     r2,  [r10, r1, LSL #2]                    ; reset system Qhandle
10
        Pull    "r2, r3"

  ]

; Issue service to tell TaskManager

        Push    "r0, r1, r2"
        MOV     r2, r1
        MOV     r1, #Service_DynamicAreaRemove
        BL      Issue_Service
        Pull    "r0, r1, r2"

        CLRV
        EXIT

; come here if shrink failed - r0-r2 stacked

80
        STR     r0, [sp]                ; overwrite stacked r0 with error pointer
        LDR     r0, [sp, #1*4]          ; reload area number
        LDR     r1, [r10, #DANode_Size] ; get size after failed shrink
        SUB     r1, r2, r1              ; change needed to restore original size
        SWI     XOS_ChangeDynamicArea   ; ignore any error from this
        Pull    "r0-r2"
        SETV
        EXIT

UnknownDyn
        ADRL    r0, ErrorBlock_BadDynamicArea
 [ International
        BL      TranslateError
 ]
90
        SETV
        EXIT

;**************************************************************************
;
;       DynArea_GetInfo - Get info on a dynamic area
;
;       Internal routine called by DynamicAreaSWI
;
; in:   r0 = reason code (2)
;       r1 = area number
;
; out:  r2 = current size of area
;       r3 = base logical address
;       r4 = area flags
;       r5 = maximum size of area
;       r6 -> area handler routine
;       r7 = workspace pointer
;       r8 -> title string
;       r10-r12 may be corrupted
;       All other registers preserved
;

DynArea_GetInfo ALTENTRY
  [ DynArea_QuickHandles
        BL      QCheckAreaNumber        ; check area exists
  |
        BL      CheckAreaNumber         ; check area exists
  ]
        BCC     UnknownDyn              ; [it doesn't]

; r10 -> node, so get info

        LDR     r2, [r10, #DANode_Size]
        LDR     r3, [r10, #DANode_Base]
        LDR     r4, [r10, #DANode_Flags]
        LDR     r5, [r10, #DANode_MaxSize]
        LDR     r6, [r10, #DANode_Handler]
        LDR     r7, [r10, #DANode_Workspace]
        LDR     r8, [r10, #DANode_Title]
        CLRV
        EXIT

;**************************************************************************
;
;       DynArea_GetChangeInfo
;
;       Get info on changes to *non-system* dynamic areas
;       Reserved for Acorn use (intended for TaskManager, can only serve one client)
;
;       Internal routine called by DynamicAreaSWI
;
; in:   r0 = reason code (6)
;
; out:  r1 = Number of affected area, if a single change has occurred since last call,
;          = -1, if no changes or more than one change have occurred
;       r2 = signature of changes to non-system dynamic areas since last call to
;            OS_DynamicArea 6
;            bit 0 = 1 if any non-system areas have been created
;            bit 1 = 1 if any non-system areas have been removed
;            bit 2 = 1 if any non-system areas have been resized
;            bit 3 = 1 if any non-system areas have been renumbered
;            bits 4-31 reserved (undefined)
;
;Notes:
; (1) bit 2 of r2 excludes the initial grow of a created area, and the final
;     shrink of a removed area
; (2) if a single renumber has occurred, r1 is the old number

  [ DynArea_QuickHandles

DynArea_GetChangeInfo ROUT
        Push    "lr"
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        LDR     r1, DynArea_OD6Handle
        LDR     r2, DynArea_OD6Signature
        LDR     lr, DynArea_OD6PrevSignature
        CMP     lr, #0
        MOVNE   r1, #-1
        CMP     r2, #0
        MOVEQ   r1, #-1
        MOV     lr, #0
        STR     lr, DynArea_OD6Signature
        STR     lr, DynArea_OD6PrevSignature
        CLRV
        Pull    "PC"

  ] ;DynArea_QuickHandles

;**************************************************************************
;
;       DynArea_EnumerateInfo
;
;       Enumerate *non-system* dynamic areas, returning selected info
;       Reserved for Acorn use (intended for TaskManager)
;
; in:   r0 = reason code (7)
;       r1 = -1 to start enumeration, or area number to continue from
;
; out:  r1 = number of next area found, or -1 if no more areas
;       r2 = current size of area, if area found
;       r3 = base logical address, if area found
;       r4 = area flags, if area found
;       r5 = maximum size of area, if area found
;       r6 -> title string, if area found
;
;Notes:
; (1) r2-r6 on exit are undefined if r1 = -1
;

  [ DynArea_QuickHandles

DynArea_EnumerateInfo ROUT
        Push    "lr"

        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]

        CMP     r1, #-1                         ; if starting from beginning
        LDREQ   r10, DynArea_SortedList         ; then load pointer to 1st node on sorted list
        BEQ     %FT10                           ; and skip

        LDR     r10, DynArea_LastEnumHandle
        CMP     r10, r1
        LDREQ   r10, DynArea_LastEnumPtr        ; pick up where we left off
        BEQ     %FT08
        BL      QCheckAreaNumber                ; else check valid area number
        BCC     %FT14

08
        LDR     r10, [r10, #DANode_SortLink]    ; find next one
10
        TEQ     r10, #0                         ; if at end
        MOVEQ   r1, #-1                         ; then return -1
        BEQ     %FT12
        LDR     r1, [r10, #DANode_Number]       ; else return number
        CMP     r1, #ChangeDyn_MaxArea
        BLS     %BT08                           ; skip if system area

        LDR     r2, [r10, #DANode_Size]         ; return rest of info
        LDR     r3, [r10, #DANode_Base]
        LDR     r4, [r10, #DANode_Flags]
        LDR     r5, [r10, #DANode_MaxSize]
        LDR     r6, [r10, #DANode_Title]

        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        STR     r1, DynArea_LastEnumHandle
        STR     r10, DynArea_LastEnumPtr        ; save a lot of messing about on next call
12
        CLRV
        Pull    "PC"

;it's a reserved call, so naff off internationalisation
14
        ADR     r0,DynArea_badei
        SETV
        Pull    "PC"
DynArea_badei
        DCD     0
        DCB     "bad DA number",0
        ALIGN

  ] ;DynArea_QuickHandles

;**************************************************************************
;
;       DynArea_SetClamps
;
;       Set clamps on max size of dynamic areas created by subsequent
;       calls to OS_DynamicArea 0
;
;   On entry
;       R0 = 8 (reason code)
;       R1 = limit on maximum size of (non-Sparse) areas created by
;            OS_DynamicArea 0 with R5 = -1, or 0 to read only
;       R2 = limit on maximum size of (non-Sparse) areas created by
;            OS_DynamicArea 0 with R5 > 0, or 0 to read only
;       R3 = limit on maximum size of Sparse areas created by
;            OS_DynamicArea 0 with R4 bit 10 set, or 0 to read only
;
;   On exit
;       R1 = previous limit for OS_DynamicArea 0 with R5 = -1
;       R2 = previous limit for OS_DynamicArea 0 with R5 > 0
;       R3 = previous limit for OS_DynamicArea 0 with R4 bit 10 set
;
;       Specifying -1 in R1 or R2 means the respective limit
;       is the RAM limit of the machine (this is the default).
;       Specifiying larger than the RAM limit in R1 or R2 is
;       equivalent to specifiying -1.
;
  [ DynArea_QuickHandles

DynArea_SetClamps ROUT
        Push    "r9,lr"

        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]

        LDR     r10, DynArea_OD8Clamp1
        LDR     lr,  DynArea_OD8Clamp2
        LDR     r9,  DynArea_OD8Clamp3

;insist on at least 1M for a clamp value (ignore anything lower)
;
        CMP     r1, #&100000
        STRHS   r1, DynArea_OD8Clamp1
        CMP     r2, #&100000
        STRHS   r2, DynArea_OD8Clamp2
        CMP     r3, #&100000
        STRHS   r3, DynArea_OD8Clamp3

        MOV     r1, r10
        MOV     r2, lr
        MOV     r3, r9

        CLRV
        Pull    "r9,PC"

  ] ;DynArea_QuickHandles

;**************************************************************************

;       DynArea_SparseClaim
;
;  Ensure region of sparse dynamic area is mapped to valid memory
;
; in:   r0 = reason code (9)
;       r1 = area number
;       r2 = base of region to claim
;       r3 = size of region to claim
;
; out:  r0-r3 preserved (error if not all of region successfully mapped)
;       r10-r12 may be corrupted
;
; action: - round base and size to page granularity
;         - scan the L2PT for each page covered
;         - find contiguous fragments of 1 or more pages that are not yet mapped-in
;         - pass each of these fragments as a psuedo DANode to OS_ChangeDynamicArea
;           via special Batcall), to 'grow' pages into each fragment
;
  [ DA_Batman

DynArea_SparseClaim ROUT
        Push    "r0-r9,lr"
        MOV     r4,#1                   ; flags operation as a claim

DynArea_SparseChange                    ; common entry point for claim and release

  [ DynArea_QuickHandles
        BL      QCheckAreaNumber        ; check area exists
  |
        BL      CheckAreaNumber         ; check area exists
  ]
        BCC     DA_naffsparse           ; area not there

        LDR     r9,[r10,#DANode_Flags]
        TST     r9,#DynAreaFlags_SparseMap
        BEQ     DA_naffsparse           ; area not sparse

        MOV     r9,#&1000               ;page size
        SUB     r9,r9,#1
        ADD     r3,r3,r2                ;base+size
        CMP     r4,#1
        BICEQ   r2,r2,r9                ;round base down to page granularity for claim
        ADDEQ   r3,r3,r9
        BICEQ   r3,r3,r9                ;round base+size up to page granularity for claim
        ADDNE   r2,r2,r9
        BICNE   r2,r2,r9                ;round base up to page granularity for release
        BICNE   r3,r3,r9                ;round base+size down to page granularity for release
        SUB     r3,r3,r2                ;rounded size

        ADD     r9,r3,r2
        LDR     r5,[r10,#DANode_Base]
        LDR     r6,[r10,#DANode_MaxSize]
        ADD     r6,r6,r5
        CMP     r2,r5
        CMPHS   r9,r5
        BLO     DA_naffsparse
        CMP     r2,r6
        CMPLS   r9,r6
        BHI     DA_naffsparse

        ADD     r5,r2,r3                   ;base+size of mapping
        LDR     r6,[r10,#DANode_SparseHWM] ;high water mark = highest claim base+size seen
        CMP     r4,#1
        BEQ     %FT08
        CMP     r5,r6
        SUBHI   r3,r6,r2                   ;for release we can save work by trimming to high water mark
        B       %FT09                      ;r3 is now trimmed size (may be <=0 for trim to nothing)
08
        CMP     r5,r6
        STRHI   r5,[r10,#DANode_SparseHWM] ;for claim remember highest base+size as high water mark
09
        SUB     SP,SP,#DANode_NodeSize  ;room for temporary DANode on stack
        MOV     r9,r10                  ;actual sparse area DANode
        MOV     r5,SP
        MOV     r6,#DANode_NodeSize
10
        LDR     r7,[r9],#4           ;copy sparse area node to temp node
        STR     r7,[r5],#4
        SUBS    r6,r6,#4
        BNE     %BT10
        ADD     r3,r2,r3             ;stop address
;
        MOV     r5,#L2PT
        ADD     r5,r5,r2,LSR #10     ;r5 -> L2PT for base (assumes 4k page)
        MOV     r8,r2                ;start address
;
;look for next fragment of region that needs to have mapping change
20
        CMP     r8,r3
        BHS     %FT50                ;done
        LDR     r6,[r5],#4           ;pick-up next L2PT entry
        CMP     r4,#0                ;if operation is a release...
        CMPEQ   r6,#0                ;...and L2PT entry is 0 (not mapped)...
        ADDEQ   r8,r8,#&1000         ;...then skip page (is ok)
        BEQ     %BT20
        CMP     r4,#0                ;if operation is a claim (not 0)...
        CMPNE   r6,#0                ;...and L2PT entry is non-0 (mapped)...
        ADDNE   r8,r8,#&1000         ;...then skip page (is ok)
        BNE     %BT20
        MOV     r1,#&1000            ;else we need to do a change (1 page so far)
30
        ADD     r9,r8,r1
        CMP     r9,r3
        BHS     %FT40
        LDR     r6,[r5],#4           ;pick-up next L2PT entry
        CMP     r4,#1                ;if operation is a release (not 1)...
        CMPNE   r6,#0                ;...and L2PT entry is non-0 (mapped)...
        ADDNE   r1,r1,#&1000         ;...then count page as needing change
        BNE     %BT30
        CMP     r4,#1                ;if operation is a claim...
        CMPEQ   r6,#0                ;...and L2PT entry is 0 (not mapped)...
        ADDEQ   r1,r1,#&1000         ;...then count page as needing change
        BEQ     %BT30
;set up pseudo DA and do Batcall to change mapping of fragment we have found
40
        MOV     r2,SP                  ;temp DANode
        STR     r8,[r2,#DANode_Base]
        ADD     r8,r8,r1
        ADD     r8,r8,#&1000           ;next address to check after fragment
        CMP     r4,#1
        MOVEQ   r9,#0                  ;start size of 0 for claim
        MOVNE   r9,r1                  ;start size of fragment size for release
        STR     r9,[r2,#DANode_Size]
        STR     r1,[r2,#DANode_MaxSize]
        MOV     r0,#ChangeDyn_Batcall
        CMP     r4,#0
        RSBEQ   r1,r1,#0               ;batshrink for release, batgrow for claim
        SWI     XOS_ChangeDynamicArea
        TEQ     r4,#0
        RSBEQ   r1,r1,#0
        LDR     r9,[r10,#DANode_Size]
        ADD     r9,r9,r1
        STR     r9,[r10,#DANode_Size]
        BVC     %BT20
;
50
        ADD     SP,SP,#DANode_NodeSize   ;drop temp DANode
        BVS     %FT52
        BL      DA_sparse_serviceandsig
        Pull    "r0-r9,PC"
;
52
        BL      DA_sparse_serviceandsig
        SETV
        STR     r0,[SP]
        Pull    "r0-r9,PC"

DA_naffsparse
        ADR     r0,DA_naffsparseinnit
        SETV
        STR     r0,[SP]
        Pull    "r0-r9,PC"
DA_naffsparseinnit
        DCD     0
        DCB     "invalid OS_DynamicArea sparse claim/release",0
        ALIGN

DA_sparse_serviceandsig ROUT
        Push    "r0,LR"
        LDR     r1,[r10,#DANode_Number]
   [ DynArea_QuickHandles
        LDR     r11,=ZeroPage
        LDR     r11,[r11, #DynArea_ws]
        LDR     r5,DynArea_OD6Signature
        STR     r5,DynArea_OD6PrevSignature
        ORR     r5,r5,#4                     ;signal a resize
        STR     r5,DynArea_OD6Signature
        STR     r1,DynArea_OD6Handle
   ]
        MOV     r2,r1                    ;area number
        MOV     r0,#0                    ;nominal 'grow/shrink' of 0 for service call
        MOV     r1,#Service_MemoryMoved
        BL      Issue_Service
        Pull    "r0,PC"

  ] ;DA_Batman

;**************************************************************************
;
;       DynArea_SparseRelease
;
;  Allow region of sparse dynamic area to release memory to free pool
;
; in:   r0 = reason code (10)
;       r1 = area number
;       r2 = base of region to release
;       r3 = size of region to release
;
; out:  r0-r3 preserved (error if not all of region successfully released)
;       r10-r12 may be corrupted
;
;
; action: - similar to DynArea_SparseClaim, but does 'shrinks' on fragments
;           that are mapped in

  [ DA_Batman

DynArea_SparseRelease ROUT
        Push    "r0-r9,lr"
        MOV     r4,#0                   ; flags operation as a release
        B       DynArea_SparseChange    ; jump to common code

  ] ;DA_Batman

;**************************************************************************
;
;       DynArea_Enumerate - Enumerate dynamic areas
;
;       Internal routine called by DynamicAreaSWI
;
; in:   r0 = reason code (3)
;       r1 = -1 to start enumeration, or area number to continue from
;
; out:  r1 = next area number or -1 if no next
;       r10-r12 may be corrupted
;       All other registers preserved

DynArea_Enumerate ALTENTRY
  [ DynArea_QuickHandles
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
  ]
        CMP     r1, #-1                         ; if starting from beginning
  [ DynArea_QuickHandles
        LDREQ   r10, DynArea_SortedList         ; then load pointer to 1st node on sorted list
  |
    [ ZeroPage = 0
        LDREQ   r10, [r1, #DAList+1]            ; then load pointer to 1st node
    |
        LDREQ   r10, =ZeroPage
        LDREQ   r10, [r10, #DAList]
    ]
  ]
        BEQ     %FT10                           ; and skip

  [ DynArea_QuickHandles
        LDR     r10, DynArea_LastEnumHandle
        CMP     r10, r1
        LDREQ   r10, DynArea_LastEnumPtr        ; pick up where we left off
        BEQ     %FT08
        BL      QCheckAreaNumber                ; else check valid area number
  |
        BL      CheckAreaNumber                 ; else check valid area number
  ]
        BCC     UnknownDyn                      ; complain if passed in duff area number

08
  [ DynArea_QuickHandles
        LDR     r10, [r10, #DANode_SortLink]    ; find next one
  |
        LDR     r10, [r10, #DANode_Link]        ; find next one
  ]
10
        TEQ     r10, #0                         ; if at end
        MOVEQ   r1, #-1                         ; then return -1
        BEQ     %FT12
        LDR     r1, [r10, #DANode_Number]       ; else return number

  [ DynArea_QuickHandles
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        STR     r1, DynArea_LastEnumHandle
        STR     r10, DynArea_LastEnumPtr        ; save a lot of messing about on next call
  ]
12
        CLRV
        EXIT

;**************************************************************************
;
;       DynArea_Renumber - Renumber dynamic area
;
;       Internal routine called by DynamicAreaSWI
;
; in:   r0 = reason code (4)
;       r1 = old area number
;       r2 = new area number
;

DynArea_Renumber ALTENTRY
        CMP     r1, #ChangeDyn_MaxArea
        BLS     %FT92                           ; can't renumber a system area
        CMP     r1, #ChangeDyn_MaxArea
        BLS     %FT92
  [ DynArea_QuickHandles
        CMP     r1, #DynArea_NewAreas
        BLO     %FT10
        CMP     r1, #DynArea_NewAreas+DynArea_NumQHandles
        BLO     %FT92                           ; can't renumber a quick handle
10
        CMP     r2, #DynArea_NewAreas
        BLO     %FT12
        CMP     r2, #DynArea_NewAreas+DynArea_NumQHandles
        BLO     %FT92                           ; can't choose your own quick handle
12
        BL      QCheckAreaNumber
  |
        BL      CheckAreaNumber                 ; check valid area number
  ]
        BCC     UnknownDyn                      ; [it's not]

        Push    "r1"
        MOV     r12, r10                        ; save pointer to node
        MOV     r1, r2
  [ DynArea_QuickHandles
        BL      QCheckAreaNumber                ; check area r2 doesn't already exist
  |
        BL      CheckAreaNumber                 ; check area r2 doesn't already exist
  ]
        Pull    "r1"
        BCS     %FT90                           ; [area r2 already exists]

        STR     r2, [r12, #DANode_Number]

; Now issue service to tell TaskManager

        Push    "r1-r3"
        MOV     r3, r2                          ; new number
        MOV     r2, r1                          ; old number
        MOV     r1, #Service_DynamicAreaRenumber
        BL      Issue_Service
        Pull    "r1-r3"

  [ DynArea_QuickHandles
        LDR     r11, =ZeroPage                         ; we know system areas cannot be renumbered
        LDR     r11, [r11, #DynArea_ws]
        MOV     r10, #-1
        STR     r10, DynArea_CreatingHandle     ; invalidate these, just in case
        STR     r10, DynArea_LastTreacleHandle
        STR     r10, DynArea_LastEnumHandle
        LDR     lr, DynArea_OD6Signature
        STR     lr, DynArea_OD6PrevSignature
        ORR     lr, lr, #8
        STR     lr, DynArea_OD6Signature
        STR     r1, DynArea_OD6Handle
  ]

        CLRV
        EXIT

90
        ADRL    r0, ErrorBlock_AreaAlreadyExists
 [ International
        BL      TranslateError
 ]
        SETV
        EXIT

;if you think this is worth internationalising, I pity you
92
        ADR     r0, DynArea_NaughtyRenum
        SETV
        EXIT
DynArea_NaughtyRenum
        DCD     0
        DCB     "illegal DA renumber",0
        ALIGN

 [ ShrinkableDAs
;**************************************************************************
;
;       DynArea_ReturnFree - Return total free space, including shrinkables
;
;       Internal routine called by DynamicAreaSWI
;
; in:   r0 = reason code (5)
;       r1 = area number to exclude, or -1 to include all shrinkable areas
;
; out:  r2 = total amount of free memory
;

DynArea_ReturnFree ALTENTRY
        CMP     r1, #-1                         ; if no excluded area,
        MOVEQ   r10, r1                         ; then point r10 nowhere
        BEQ     %FT10

  [ DynArea_QuickHandles
        BL      QCheckAreaNumber                ; else check area number is valid
  |
        BL      CheckAreaNumber                 ; else check area number is valid
  ]
        BCC     UnknownDyn                      ; [unknown area]
10
        LDR     r2, =ZeroPage
        LDR     r2, [r2, #FreePoolDANode + DANode_Size] ; start with current size of free pool
  [ DynArea_QuickHandles
        ;traverse the Shrinkable sublist
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        LDR     r11, DynArea_ShrinkableSubList
        B       %FT21
  |
        LDR     r11, =ZeroPage+DAList
  ]
20
  [ DynArea_QuickHandles
        LDR     r11, [r11, #DANode_SubLink]     ; load next area on sublist
  |
        LDR     r11, [r11, #DANode_Link]        ; load next area
  ]
21
        TEQ     r11, #0                         ; if end of list
        EXIT    EQ                              ; then exit, with r2 = correct value

  [ DynArea_QuickHandles
        TEQ     r11, r10                        ; must not be the excluded area
  |
        LDR     lr, [r11, #DANode_Flags]        ; load area flags
        TST     lr, #DynAreaFlags_Shrinkable    ; must be shrinkable
        TEQNE   r11, r10                        ; and not excluded area
  ]
        BEQ     %BT20                           ; [don't try this one]

        Push    r3
        BL      CallTestShrink
        ADD     r2, r2, r3                      ; add on amount if any
        Pull    r3
        B       %BT20                           ; then go back for more
 ]

;**************************************************************************
;
;       CheckAreaNumber - Try to find area with number r1
;
;       Internal routine called by DynArea_Create
;
; in:   r1 = area number to match
; out:  If match, then
;         C=1, r10 -> node, r11 -> previous node
;       else
;         C=0, r10,r11 corrupted
;       endif

CheckAreaNumber Entry
  [ DynArea_QuickHandles
QCheckAreaNumber_nonQ
  ]
        LDR     r10, =ZeroPage+DAList
        ASSERT  DANode_Link = 0                 ; because DAList has only link
10
        MOV     r11, r10                        ; save prev
        LDR     r10, [r10, #DANode_Link]        ; and load next
        CMP     r10, #1                         ; any more nodes?
        EXIT    CC                              ; no, then no match
        LDR     lr, [r10, #DANode_Number]       ; get number
        CMP     lr, r1                          ; does number match
        BNE     %BT10                           ; no, try next
  [ DynArea_QuickHandles
        Push    "r11"
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        STR     r1,  DynArea_LastTreacleHandle
        STR     r10, DynArea_LastTreaclePtr
        Pull    "r11"
  ]
        EXIT                                    ; (C=1 from CMP lr,r1)

  [ DynArea_QuickHandles

; QCheckAreaNumber - similar to CheckAreaNumber, but blisteringly quick for system or for quick
;                    numbers. However, DOES NOT return previous node in r11.
;
QCheckAreaNumber ROUT
        Push    "lr"
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        CMP     r1, #-1
        BEQ     QCheckAreaNumber_nonQ           ;just to protect against -1 as a proposed number
        LDR     lr, DynArea_LastTreacleHandle
        CMP     lr, r1
        LDREQ   r10, DynArea_LastTreaclePtr
        EXIT    EQ                              ;found node is slow one we handled last time (carry set)
        LDR     lr, DynArea_CreatingHandle
        CMP     lr, r1
        LDREQ   r10, DynArea_CreatingPtr
        EXIT    EQ                              ;found node is one we're creating (carry set)
        CMP     r1, #ChangeDyn_MaxArea
        BLS     %FT10
        CMP     r1,#DynArea_NewAreas
        BLO     QCheckAreaNumber_nonQ
        SUB     lr, r1, #DynArea_NewAreas
        CMP     lr, #DynArea_NumQHandles
        BHS     QCheckAreaNumber_nonQ
;quick handle
        ADR     r10, DynArea_QHandleArray
        LDR     r10, [r10, lr, LSL #2]
        CMP     r10, #DynArea_NumQHandles
        MOVLS   r10, #0                         ;not a valid pointer
        CMP     r10, #1
        Pull    "pc"                            ;handle free, carry clear
                                                ;or word is ptr to node, carry set
;system handle
10
        ADR     r10, DynArea_SysQHandleArray
        LDR     r10, [r10, r1, LSL #2]
        CMP     r10, #1
        Pull    "pc"                            ;DA does not exist, carry clear
                                                ;or word is ptr to node, carry set
  ] ;DynArea_QuickHandles

;**************************************************************************
;
;       CheckForOverlappingAreas - Check that given area does not overlap any existing ones
;
;       Internal routine called by DynArea_Create
;
; in:   r3 = base address
;       r4 = area flags (NB if doubly mapped, then have to check both halves for overlap)
;       r5 = size (of each half in doubly mapped areas)
;
; out:  If this area overlaps with an existing one, then
;         r0 -> error
;         V=1
;       else
;         r0 preserved
;         V=0
;       endif
;

CheckForOverlappingAreas Entry "r0-r5"
        TST     r4, #DynAreaFlags_DoublyMapped          ; check if doubly mapped
        BEQ     %FT05                                   ; [not, so don't mangle]

        SUBS    r3, r3, r5                              ; move start address back
        BCC     %FT20                                   ; oh dear! - it went back to below 0
        MOVS    r5, r5, LSL #1                          ; and double size
        BCS     %FT20                                   ; if that wrapped then that's bad, too
05
        ADDS    r5, r5, r3                              ; r5 -> end +1
        BHI     %FT20                                   ; if CS, indicating wrap, and not EQ (ie just ending at 0), then bad

  [ HAL
        LDR     lr, =ZeroPage
        LDR     r0, [lr, #IOAllocPtr]
        CMP     r5, r0                                  ; end must be below I/O space (allocated down from high memory)
        BHI     %FT20
  ]

; check against list of fixed areas

        ADR     lr, FixedAreasTable
10
        LDMIA   lr!, {r0, r1}                           ; r0 = start addr, r1 = size
        CMP     r0, #-1                                 ; if at end of list
        BEQ     %FT30                                   ; then OK wrt fixed areas
        ADD     r1, r1, r0                              ; r1 = end addr+1
        CMP     r5, r0                                  ; if end of our area is <= start of fixed, then OK wrt fixed areas
        BLS     %FT30
        CMP     r3, r1                                  ; if start of our area is >= end of fixed, then go onto next area
        BCS     %BT10

20
        ADRL    r0, ErrorBlock_OverlappingAreas
 [ International
        BL      TranslateError
 ]
        STR     r0, [sp]
        SETV
        EXIT

; Now, check against DAList

30
        LDR     lr, =ZeroPage+DAList
        ASSERT  DANode_Link = 0
40
        LDR     lr, [lr, #DANode_Link]
        CMP     lr, #0                                  ; if got to end of list (V=0)
        BEQ     %FT50                                   ; then exit saying OK
        LDR     r0, [lr, #DANode_Base]
        LDR     r1, [lr, #DANode_Flags]
        TST     r1, #DynAreaFlags_DoublyMapped
        LDR     r1, [lr, #DANode_MaxSize]
        SUBNE   r0, r0, r1                              ; if doubly mapped then move back
        MOVNE   r1, r1, LSL #1                          ; and double size
        ADD     r1, r1, r0                              ; r1 -> end
        CMP     r5, r0                                  ; if end of our area is <= start of dyn, then OK wrt dyn areas)
        BLS     %FT50
        CMP     r3, r1                                  ; if start of our area is >= end of dyn, then go onto next area
        BCS     %BT40
        B       %BT20                                   ; else it overlaps

50
        CLRV                                            ; OK exit
        EXIT


FixedAreasTable                                         ; table of fixed areas (address, size)
 [ HAL
        &       0,                      AplWorkMaxSize  ; application space
 [ CursorChunkAddress < IO
        &       CursorChunkAddress,     64*1024                 ; 32K for cursor, 32K for "nowhere"
 ]
 [ ROM < IO
 [ OSROM_ImageSize > 8192
        &       &03800000,              OSROM_ImageSize*1024   ; ROM
 |
        &       &03800000,              8*1024*1024            ; ROM
 ]
 ]
        &       IO,                     &FFFFFFFF-IO    ; Kernel workspace (code will also check dynamic value IOAllocPtr)
 |
        &       0,                      AplWorkMaxSize  ; application space
        &       UndStackSoftCamChunk,   1024*1024               ; undefined stack / soft cam map
        &       CursorChunkAddress,     1504*1024               ; cursor, Nowhere, cache cleaners, reserved (up to kernel buffers)
        &       L2PT,                   4*1024*1024             ; L2PT (and L1PT)
 [ OSROM_ImageSize > 8192
        &       &03000000,              8*1024*1024+OSROM_ImageSize*1024    ; I/O + ROM
 |
        &       &03000000,              16*1024*1024            ; I/O + ROM
 ]
        &       PhysSpace,              512*1024*1024   ; PhysSpace
 [ No26bitCode
        &       AbtStack,               AbtStackSize
 ]
 [ ShadowROM
        &       &FF800000,              &007FFFFF       ; Shadow ROM (length has been bodged to avoid wrap problems)
 ]
 ] ; HAL
        &       -1,                     0               ; termination

;**************************************************************************
;
;       AllocateAreaAddress - Find an area of logical space to use for this area
;
;       Internal routine called by DynArea_Create
;
; in:   r4 = area flags (NB if doubly mapped, we have to find space for both halves)
;       r5 = size (of each half in doubly mapped areas)
;
; out:  If successfully found an address, then
;         r0 preserved
;         r3 = logical address
;         V=0
;       else
;         r0 -> error
;         r3 preserved
;         V=1
;       endif

AllocateAreaAddress Entry "r0-r2,r4-r7"
        TST     r4, #DynAreaFlags_DoublyMapped          ; check if doubly mapped
        BEQ     %FT05                                   ; [not, so don't mangle]
        MOVS    r5, r5, LSL #1                          ; double size
        BCS     %FT90                                   ; if that wrapped then that's bad
05
        LDR     r3, =DynArea_NewAreasBase               ; r3 is our current attempt
        ADR     r0, FixedAreasTable                     ; r0 is ptr into fixed areas table
        LDR     r1, =ZeroPage+DAList                             ; r1 is ptr into dyn areas list
10
        ADDS    r7, r3, r5                              ; r7 is our end+1
        BHI     %FT90                                   ; if we wrapped (but not end+1=0) then we failed
  [ HAL
        LDR     lr, =ZeroPage
        LDR     r2, [lr, #IOAllocPtr]
        CMP     r7, r2
        BHI     %FT90                                   ; if we walked into IOspace (assumed higher than any DA space) then we failed
  ]
15
        BL      GetNextRange                            ; get next range from either list (r2=start, r6=end+1)
        CMP     r7, r2                                  ; if end(ours) <= start(next) then this is OK
        BLS     %FT80                                   ; (note this also works when r2=-1)
        CMP     r3, r6                                  ; else if start(ours) >= end(next)
        BCS     %BT15                                   ; then get another
        MOV     r3, r6                                  ; else make start(ours) := end(next)
        B       %BT10                                   ; and go back for another try

; we've succeeded - just apply unbodge for doubly-mapped areas

80
        TST     r4, #DynAreaFlags_DoublyMapped          ; if doubly mapped
        MOVNE   r5, r5, LSR #1                          ; halve size again
        ADDNE   r3, r3, r5                              ; and advance base address to middle
        CLRV
        EXIT

90
        ADRL    r0, ErrorBlock_CantAllocateArea
  [ International
        BL      TranslateError
  ]
        STR     r0, [sp]
        SETV
        EXIT                                    ; say we can't do it

        LTORG

;**************************************************************************
;
;       GetNextRange - Get next lowest range from either fixed or dynamic list
;
;       Internal routine called by AllocateAreaAddress
;
; in:   r0 -> next entry in fixed list
;       r1!0 -> next entry in dyn list
;
; out:  r2 = next lowest area base (-1 if none)
;       r6 = end of that range (undefined if none)
;       Either r0 or r1 updated to next one (except when r2=-1 on exit)
;

GetNextRange Entry "r7,r8"
        LDMIA   r0, {r2, r6}                            ; load start, size from fixed list
        ADD     r6, r6, r2                              ; r6 = end+1

        ASSERT  DANode_Link = 0
        LDR     r7, [r1, #DANode_Link]                  ; get next from dyn
        TEQ     r7, #0                                  ; if none
        MOVEQ   r8, #-1                                 ; then use addr -1
        BEQ     %FT10

        LDR     r8, [r7, #DANode_Flags]                 ; more double trouble
        TST     r8, #DynAreaFlags_DoublyMapped
        LDR     r8, [r7, #DANode_Base]
        LDR     lr, [r7, #DANode_MaxSize]
        SUBNE   r8, r8, lr
        MOVNE   lr, lr, LSL #1
        ADD     lr, lr, r8                              ; now r8 = start addr, lr = end+1
10
        CMP     r8, r2                                  ; if dyn one is earlier
        MOVCC   r2, r8                                  ; then use dyn start
        MOVCC   r6, lr                                  ; and end
        MOVCC   r1, r7                                  ; and advance dyn ptr
        EXIT    CC                                      ; then exit
        CMP     r2, #-1                                 ; else if not at end of fixed
        ADDNE   r0, r0, #8                              ; then advance fixed ptr
        EXIT

;**************************************************************************
;
;       AllocateBackingLevel2 - Allocate L2 pages for an area
;
;       Internal routine called by DynArea_Create
;
; in:   r3 = base address (will be page aligned)
;       r4 = area flags (NB if doubly mapped, then have to allocate for both halves)
;       r5 = size (of each half in doubly mapped areas)
;
; out:  If successfully allocated pages, then
;         All registers preserved
;         V=0
;       else
;         r0 -> error
;         V=1
;       endif

AllocateBackingLevel2 Entry "r0-r8,r11"
        TST     r4, #DynAreaFlags_DoublyMapped          ; if doubly mapped
        SUBNE   r3, r3, r5                              ; then area starts further back
        MOVNE   r5, r5, LSL #1                          ; and is twice the size

; NB no need to do sanity checks on addresses here, they've already been checked

; now round address range to 4M boundaries

        ADD     r5, r5, r3                              ; r5 -> end
        MOV     r0, #1 :SHL: 22
        SUB     r0, r0, #1
        BIC     r8, r3, r0                              ; round start address down (+ save for later)
        ADD     r5, r5, r0
        BIC     r5, r5, r0                              ; but round end address up

; first go through existing L2PT working out how much we need

        LDR     r7, =L2PT
        ADD     r3, r7, r8, LSR #10                     ; r3 -> start of L2PT for area
        ADD     r5, r7, r5, LSR #10                     ; r5 -> end of L2PT for area +1

        ADD     r1, r7, r3, LSR #10                     ; r1 -> L2PT for r3
        ADD     r2, r7, r5, LSR #10                     ; r2 -> L2PT for r5

        TEQ     r1, r2                                  ; if no pages needed
        BEQ     %FT30

        MOV     r4, #0                                  ; number of backing pages needed
10
        LDR     r6, [r1], #4                            ; get L2PT entry for L2PT
        TST     r6, #3                                  ; EQ if translation fault
        ADDEQ   r4, r4, #1                              ; if not there then 1 more page needed
        TEQ     r1, r2
        BNE     %BT10

; if no pages needed, then exit

        TEQ     r4, #0
        BEQ     %FT30

; now we need to claim r4 pages from the free pool, if possible; return error if not

        LDR     r1, =ZeroPage
        LDR     r6, [r1, #FreePoolDANode + DANode_Size]
        SUBS    r6, r6, r4, LSL #12                     ; reduce free pool size by that many pages
        BCS     %FT14                                   ; if enough, skip next bit

; not enough pages in free pool currently, so try to grow it by the required amount

        Push    "r0, r1"
        MOV     r0, #ChangeDyn_FreePool
        RSB     r1, r6, #0                              ; size change we want (+ve)
        SWI     XOS_ChangeDynamicArea
        Pull    "r0, r1"
        BVS     %FT90                                   ; didn't manage change, so report error

        MOV     r6, #0                                  ; will be no pages left in free pool after this
14
        STR     r6, [r1, #FreePoolDANode + DANode_Size] ; if possible then update size

; after that we need to zero all these pages out (=> cause translation fault for area initially)

        LDR     r0, [r1, #FreePoolDANode + DANode_Base] ; r0 -> base of free pool
        ADD     r0, r0, r6                              ; r0 -> first byte we're taking out of free pool
        ADD     r6, r0, r4, LSL #12                     ; r6 -> byte after last in free pool
        Push    r8                                      ; save original logical address
      [ ZeroPage <> 0
        MOV     r1, #0
      ]
        MOV     r8, #0                                  ; 0 => translation fault
        MOV     r11, #0
        MOV     lr, #0
15
        STMIA   r0!, {r1,r8,r11,lr}                     ; store data
        TEQ     r0, r6
        BNE     %BT15
        Pull    r8

; now r0 -> after end of free pool (log addr)

        LDR     lr, =L1PT
        ADD     r8, lr, r8, LSR #18                     ; point r8 at start of L1 we may be updating
        ADD     r1, r7, r3, LSR #10                     ; point r1 at L2PT for r3 again
        MOV     r11, #AP_L2PT                           ; access privs (+CB bits)
20
        LDR     r6, [r1], #4                            ; get L2PT entry again
        TST     r6, #3                                  ; if no fault
        BNE     %FT25                                   ; then skip

        SUB     r0, r0, #4096                           ; move free pointer back
        Push    "r2,r4,r5"
        BL      MoveCAMatR0toR3                         ; else move page from end of free pool to r3
        Pull    "r2,r4,r5"

        LDR     lr, =ZeroPage
        LDR     r6, [lr, #L2PTUsed]
        ADD     r6, r6, #4096
        STR     r6, [lr, #L2PTUsed]

; now update 4 words in L1PT (corresponding to 4M of address space which is covered by the 4K of L2)
; and point them at the physical page we've just allocated (r1!-4 will already hold physical address+bits now!)

        LDR     r6, [r1, #-4]                           ; r6 = physical address for L2 page + other L2 bits
        MOV     r6, r6, LSR #12                         ; r6 = phys.addr >> 12
 [ MEMM_Type = "VMSAv6"
        LDR     lr, =L1_Page
 |
  [ ECC
        LDR     lr, =L1_Page + L1_U + L1_P              ; form other bits to put in L1
  |
        LDR     lr, =L1_Page + L1_U                     ; form other bits to put in L1
  ]
 ]
        ORR     lr, lr, r6, LSL #12                     ; complete L1 entry
        STR     lr, [r8, #0]                            ; store entry for 1st MB
        ADD     lr, lr, #1024                           ; advance L2 pointer
        STR     lr, [r8, #4]                            ; store entry for 2nd MB
        ADD     lr, lr, #1024                           ; advance L2 pointer
        STR     lr, [r8, #8]                            ; store entry for 3rd MB
        ADD     lr, lr, #1024                           ; advance L2 pointer
        STR     lr, [r8, #12]                           ; store entry for 4th MB
        ;;;ARM_flush_TLB r6                junk TLB(s) shouldn't be needed, would have to be ARMop now
25
        ADD     r3, r3, #4096                           ; advance L2PT logical address
        ADD     r8, r8, #16                             ; move onto L1 for next 4M

        TEQ     r1, r2
        BNE     %BT20
30
        CLRV
        EXIT

; Come here if not enough space in free pool to allocate level2

90
        ADRL    r0, ErrorBlock_CantAllocateLevel2
  [ International
        BL      TranslateError
  ]
        STR     r0, [sp]
        SETV
        EXIT

;**************************************************************************

  [ ChocolateSysHeap
;
; CreateChocolateBlockArray
;
; entry: r2 = No. of blocks to be created in array (N)
;        r3 = size of each block in bytes (S, must be multiple of 4)
;
; exit:
;        r2 = address of chocolate block array (parent SysHeap block address)
;        array is initialised to all blocks free
;   OR   V set, r0=error pointer, if error (from OS_Heap)
;
; - A Chocolate block array is only suitable for blocks of single fixed size.
;   These blocks may be allocated and freed in any order, but never resized.
; - Allocating and freeing blocks from a Chocolate block array is much faster
;   than OS_Heap, and the cost of operations is independent of N.
; - A Chocolate block array is a single block in the SysHeap, and looks like this
;   (excluding the internal structure of OS_Heap blocks):
;
;  - array header (3 words):
;    - word 0  =  total no. of blocks in array (N)
;    - word 1  =  size of each block container (S+4)
;    - word 2  -> first free block container in array, or 0 if none free
;  - followed immediately by N block containers, each of form:
;    - container header (1 word):
;      - word 0 :  bits  0..30 = container id (C)
;                                C = (S+4)*I, where I is array index, 0..N-1
;                  bit  31     = 1 if block is free, 0 if block is in use
;    - followed immediately by the S/4 words of the block itself
;      - if the block is in use, these words are as defined by the client
;      - if the block is free, the first word is a pointer to the next free
;        block container (or 0 if end of free list), and the other words
;        are undefined
;
; - A Chocolate block array requires a SysHeap block of 3*4 + N*(S + 4) bytes
;
CreateChocolateBlockArray ROUT
        Push    "r0,r1,r3,r4,r5,lr"
        MOV     r5,r2                ;N
        ADD     r4,r3,#4             ;S+4
        MUL     r3,r5,r4
        ADD     r3,r3,#3*4
        BL      ClaimSysHeapNode
        STRVS   r0,[SP]
        BVS     %FT50
        STR     r5,[r2]
        STR     r4,[r2,#4]
        ADD     r1,r2,#3*4
        STR     r1,[r2,#8]
        MOV     lr,r5
        ADD     r0,r2,#3*4
        MOV     r1,#&80000000        ;free flag
10
        STR     r1,[r0]
        ADD     r3,r0,r4
        STR     r3,[r0,#4]
        ADD     r1,r1,r4
        SUBS    lr,lr,#1
        MOVNE   r0,r3
        BNE     %BT10
        MOV     r1,#0
        STR     r1,[r0,#4]           ;end of free list
50
        Pull    "r0,r1,r3,r4,r5,pc"

;
; ClaimChocolateBlock
;
; entry: r3 = address of parent ChocolateBlockArray (must be valid)
; exit:  r2 = address of allocated block
;        r3 = size of block
;  OR    V set, R0=error (no free blocks)
;
ClaimChocolateBlock ROUT
        Push    "r1,r4,lr"
        MRS     r4,CPSR
        ORR     r1,r4,#I32_bit
        MSR     CPSR_c,r1         ;protect critical manipulation from interrupt re-entry
        LDR     r2,[r3,#8]        ;pick up block container at front of free list
        CMP     r2,#0
        BEQ     ClaimChocolateBlock_NoneFree
        LDR     r1,[r2]
        BIC     r1,r1,#&80000000  ;clear the free flag
        STR     r1,[r2]
        LDR     r1,[r2,#4]        ;next free block container
        STR     r1,[r3,#8]        ;put it at front
        ADD     r2,r2,#4          ;address of block
        LDR     r3,[r3,#4]
        SUB     r3,r3,#4          ;size of block
        BIC     r4,r4,#V_bit      ;return with V clear
        MSR     CPSR_cf,r4        ;restore IRQ state
        Pull    "r1,r4,pc"
;
;DON'T even think about internationalisation - this exit route must be fast
ClaimChocolateBlock_NoneFree
        ADR     R0,ChocolateBlock_NFError
        ORR     r4,r4,#V_bit      ;return with V set
        MSR     CPSR_cf,r4        ;restore IRQ state
        Pull    "r1,r4,pc"

;
; FreeChocolateBlock
;
; entry: r1 = address of parent ChocolateBlockArray (must be valid)
;        r2 = address of block to free (may be invalid)
; exit:  -
;   OR   V set, R0=error (not a ChocolateBlock), r1,r2 still preserved
;
FreeChocolateBlock ROUT
        Push    "r2,r3,r4,lr"
        MRS     r4,CPSR
        ORR     r3,r4,#I32_bit
        MSR     CPSR_c,r3         ;protect critical manipulation from interrupt re-entry
        ADD     r3,r1,#12         ;r3 -> first block container
        SUB     r2,r2,#4          ;r2 -> container for block (if valid)
        CMP     r2,r3
        BLO     FreeChocolateBlock_NaffOff
        LDR     lr,[r1,#-4]       ;OS_Heap's size word (naughty!)
        ADD     lr,lr,r1
        CMP     r2,lr
        BHS     FreeChocolateBlock_NaffOff
        LDR     lr,[r2]           ;block container id
        TST     lr,#&80000000     ;free flag
        BNE     FreeChocolateBlock_NaffOff
        ADD     lr,lr,r3          ;lr := address of block container, from container id
        CMP     lr,r2
        BNE     FreeChocolateBlock_NaffOff
;
;we now believe caller is freeing a valid block, currently in use
;
        LDR     lr,[r2]
        ORR     lr,lr,#&80000000
        STR     lr,[r2]           ;set free flag in container id
        LDR     lr,[r1,#8]        ;current front of free list
        STR     lr,[r2,#4]        ;chain free list to block container we are freeing
        STR     r2,[r1,#8]        ;put freed block container at front
        BIC     r4,r4,#V_bit      ;return with V clear
        MSR     CPSR_cf,r4        ;restore IRQ state
        Pull    "r2,r3,r4,pc"
;
;DON'T even think about internationalisation - this exit route must be fast
FreeChocolateBlock_NaffOff
        ADR     R0,ChocolateBlock_NOError
        ORR     r4,r4,#V_bit      ;return with V set
        MSR     CPSR_cf,r4        ;restore IRQ state
        Pull    "r2,r3,r4,pc"

;
;forget internationalisation - if these errors aren't dealt with silently,
;                              the kernel's stuffed anyway
ChocolateBlock_NFError
        DCD     0
        DCB     "Chocolate SysHeap claim failed",0
        ALIGN
ChocolateBlock_NOError
        DCD     0
        DCB     "not a Chocolate SysHeap block",0
        ALIGN

  ] ;ChocolateSysHeap

;**************************************************************************
;
;       InitDynamicAreas - Initialise nodes for dynamic areas
;
;       It only initialises free pool, appspace and sysheap nodes
;       The other areas are created properly, after the screen area has been created (improperly)
;
; in:   -
; out:  -
;

InitDynamicAreas Entry "r0-r8,r11,r12"
        LDR     lr, =ZeroPage+AppSpaceDANode
        ADR     r0, InitAppSpaceTable
        LDMIA   r0, {r0-r8}
        STMIA   lr, {r0-r8}

        LDR     lr, =ZeroPage+FreePoolDANode
        ADR     r0, InitFreePoolTable
        LDMIA   r0, {r0-r8}                     ; copy initial data into node
        LDR     r5, [lr, #RAMLIMIT-FreePoolDANode] ; max size is RAMLIMIT
        STMIA   lr, {r0-r8}


; We have to move all free pages (ie ones not occupied by the static pages) into the free pool
; The lowest numbered physical pages must be put in last, so that when ReadCMOSAndConfigure is
; called to put in the screen, it will get the pages starting at 0 (when the screen is created
; as a dynamic area, this limitation can be removed).

; The free pages consist of two chunks of pages, each of which having consecutive physical page
; numbers. We start at MaxCamEntry and move back until we hit the end of the statics (which are
; at the start of the 1st non-video-RAM chunk). We then separately do the video RAM.

 [ HAL
        MOV     r11, r3                         ; r11 = PPL (inc CB) for free pool
        MOV     r6, r2                          ; r6 = base address of free pool, ie where to put stuff
        LDR     r5, =ZeroPage                   ; r5 = amount of memory in free pool so far (and ptr to 0)
        LDR     r0, [r5, #InitUsedStart]
        ADD     r0, r0, #DRAMOffset_FirstFixed - DRAMOffset_L1PT
        BL      PhysAddrToPageNo
        MOV     r7, r0                          ; r7 = page number of start of static chunk
        LDR     r0, [r5, #InitUsedEnd]
        BL      PhysAddrToPageNo
        MOV     r8, r0                          ; r8 = page number of 1st page in 1st chunk not used for statics

        LDR     r2, [r5, #MaxCamEntry]          ; r2 = 1st page to put in free pool
        MOV     r3, r6                          ; r3 = base address of free pool, ie where to put stuff
        SUB     lr, r8, r7
        SUB     lr, r2, lr
        ADD     r3, r3, lr, LSL #12             ; r3 = end address of free pool
      [ ZeroPage <> 0
        MOV     r5, #0
      ]

        ! 0, "Sort out static check in InitDynamicAreas"
10
        CMP     r2, r8                          ; are we into statics already
        SUBCC   r2, r7, #1                      ; if so, then move to last page of video RAM
        MOVCC   r8, #0                          ; and move barrier so we never hit it again
        BL      BangCamUpdate
        SUB     r3, r3, #4096                   ; advance logical address
        ADD     r5, r5, #4096
        SUBS    r2, r2, #1                      ; decrement page number
        BCS     %BT10                           ; if we haven't gone negative, then loop
 |
        MOV     r11, r3                         ; r11 = PPL (inc CB) for free pool
        MOV     r3, r2                          ; r3 = base address of free pool, ie where to put stuff
        LDR     r5, =ZeroPage                   ; r5 = amount of memory in free pool so far (and ptr to 0)
        LDR     r2, [r5, #MaxCamEntry]          ; r2 = 1st page to put in free pool
        LDR     r7, [r5, #VideoSize]            ; r7 = size of video RAM
      [ ZeroPage <> 0
        MOV     r5, #0
      ]
        MOV     r7, r7, LSR #12                 ; r7 = page number of start of static chunk
        ASSERT  SoftCamMapSize = L2PTSize +4
        LDR     r0, =ZeroPage+L2PTSize
        LDMIA   r0, {r0, r8}                    ; r0 = L2PTSize, r8 = SoftCamMapSize
        ADD     r8, r8, r0                      ; add sizes together
        ADD     r8, r8, #StaticPagesSize + UndStackSize ; + number of bytes used for other static bits
        ADD     r8, r7, r8, LSR #12             ; r8 = page number of 1st page in 1st chunk not used for statics
10
        CMP     r2, r8                          ; are we into statics already
        SUBCC   r2, r7, #1                      ; if so, then move to last page of video RAM
        MOVCC   r8, #0                          ; and move barrier so we never hit it again
        BL      BangCamUpdate
        ADD     r3, r3, #4096                   ; advance logical address
        ADD     r5, r5, #4096
        SUBS    r2, r2, #1                      ; decrement page number
        BCS     %BT10                           ; if we haven't gone negative, then loop
 ]

        LDR     lr, =ZeroPage+FreePoolDANode    ; may be used below to update DAList head ptr
        STR     r5, [lr, #DANode_Size]          ; update size of free pool in node

; Now initialise the system heap by hand, so we can start creating dynamic areas

        LDR     r0, =SysHeapStart
        LDR     r1, =magic_heap_descriptor
        MOV     r2, #Nil
        MOV     r3, #hpdsize
        MOV     r4, #32*1024 - (SysHeapStart-SysHeapChunkAddress)
        STMIA   r0, {r1-r4}

        LDR     r0, =ZeroPage                   ; initialise module list to empty
      [ ZeroPage = 0
        STR     r0, [r0, #Module_List]
      |
        MOV     lr, #0
        STR     lr, [r0, #Module_List]
      ]

        LDR     lr, =ZeroPage+SysHeapDANode     ; initialise system heap node
        ADR     r0, InitSysHeapTable
        LDMIA   r0, {r0-r8}
        STMIA   lr, {r0-r8}
        LDR     r0, =ZeroPage
 [ FreePoolAddress < SysHeapStart
        ADD     lr, r0, #FreePoolDANode             ; cf comments above/below - what was old code? KJB
 ]
        STR     lr, [r0, #DAList]               ; store pointer to 1st node on list (either free pool or sys heap)

  [ ChocolateSysHeap
        ASSERT  ChocolateCBBlocks = ChocolateBlockArrays +  0
        ASSERT  ChocolateSVBlocks = ChocolateBlockArrays +  4
        ASSERT  ChocolateTKBlocks = ChocolateBlockArrays +  8
        ASSERT  ChocolateMRBlocks = ChocolateBlockArrays + 12
        ASSERT  ChocolateMABlocks = ChocolateBlockArrays + 16
        ASSERT  ChocolateMSBlocks = ChocolateBlockArrays + 20
        LDR     r1,=ZeroPage+ChocolateBlockArrays
        MOV     r2,#MaxChocolateCBBlocks
        MOV     r3,#3*4
        BL      CreateChocolateBlockArray       ; better not fail
        STR     r2,[r1,#0]
        MOV     r2,#MaxChocolateSVBlocks
        MOV     r3,#VecNodeSize
        BL      CreateChocolateBlockArray       ; better not fail
        STR     r2,[r1,#4]
        MOV     r2,#MaxChocolateTKBlocks
        MOV     r3,#TickNodeSize
        BL      CreateChocolateBlockArray       ; better not fail
        STR     r2,[r1,#8]
        MOV     r2,#MaxChocolateMRBlocks
        MOV     r3,#ROMModule_NodeSize
        BL      CreateChocolateBlockArray       ; better not fail
        STR     r2,[r1,#12]
        MOV     r2,#MaxChocolateMABlocks
        MOV     r3,#ModInfo + Incarnation_Postfix + 8
        BL      CreateChocolateBlockArray       ; better not fail
        STR     r2,[r1,#16]
        MOV     r2,#MaxChocolateMSBlocks
        MOV     r3,#ModSWINode_Size
        BL      CreateChocolateBlockArray       ; better not fail
        STR     r2,[r1,#20]

  ]

  [ Oscli_HashedCommands
        MOV     r3,#4*(Oscli_MHashValMask+1)
        BL      ClaimSysHeapNode                ; better not fail
        LDR     r0,=ZeroPage
      [ ZeroPage = 0
        STR     r0,[r0,#Oscli_CmdHashSum]
      |
        MOV     r3,#0
        STR     r3,[r0,#Oscli_CmdHashSum]
      ]
        STR     r2,[r0,#Oscli_CmdHashLists]
        MOV     r3,#Oscli_MHashValMask+1
      [ ZeroPage <> 0
        MOV     r0, #0
      ]
DynArea_OHinit_loop
        STR     r0,[r2],#4
        SUBS    r3,r3,#1
        BNE     DynArea_OHinit_loop
  ]

  [ DynArea_QuickHandles

        LDR     r3, =DynArea_ws_size
        BL      ClaimSysHeapNode                    ; should not give error - kernel boot
        LDR     r0, =ZeroPage
        STR     r2, [r0, #DynArea_ws]
        MOV     r11, r2
        LDR     r3, =DynArea_ws_size
        ADD     r3, r3, r2
      [ ZeroPage <> 0
        MOV     r0, #0
      ]
DynArea_QHinit_loop1
        STR     r0, [r2], #4
        CMP     r2, r3
        BLO     DynArea_QHinit_loop1

        MOV     r1, #-1
        STR     r1, DynArea_CreatingHandle
        STR     r1, DynArea_LastTreacleHandle
        STR     r1, DynArea_LastEnumHandle
        STR     r1, DynArea_OD8Clamp1
        STR     r1, DynArea_OD8Clamp2
        MOV     r1, #&1000
        RSB     r1, r1, #0
        STR     r1, DynArea_OD8Clamp3               ; 4G - 4k

        ADR     r1, DynArea_QHandleArray            ; init all Qhandles as free, free list = 1..DynArea_NumQHandles
        MOV     r2, #1
        STR     r2, DynArea_FreeQHandles
DynArea_QHinit_loop2
        ADD     r2, r2, #1
        STR     r2, [r1], #4
        CMP     r2, #DynArea_NumQHandles
        BLO     DynArea_QHinit_loop2
        MOV     r2, #0
        STR     r2, [r1]                            ; 0 = end of free list

        MOV     r1, #DynArea_NewAreas-1
        ADD     r1, r1, #DynArea_NumQHandles        ; first non-quick DA number -1
        STR     r1, DynArea_TreacleGuess

        LDR     r1, =ZeroPage+FreePoolDANode
        STR     r1, DynArea_SortedList              ; initially, FreePool at front of sorted list,
        LDR     r2, =ZeroPage+SysHeapDANode
        STR     r2, [r1, #DANode_SortLink]          ; and SysHeap second...
        MOV     r1, #0
        STR     R1, [r2, #DANode_SortLink]          ; ...and last

        ADR     r1, DynArea_SysQHandleArray
        LDR     r2, =ZeroPage+SysHeapDANode
        STR     r2, [r1, #ChangeDyn_SysHeap:SHL:2]
        LDR     r2, =ZeroPage+FreePoolDANode
        STR     r2, [r1, #ChangeDyn_FreePool:SHL:2]

  |
        ASSERT  ZeroPage = 0
        MOV     r0, #0
        STR     r0, [r0, #DynArea_ws]

  ] ;DynArea_QuickHandles

        LDR     r0, =ZeroPage
      [ ZeroPage = 0
        STR     r0, [r0, #CDASemaphore]         ; clear CDASemaphore
      |
        MOV     r2, #0
        STR     r2, [r0, #CDASemaphore]         ; clear CDASemaphore
      ]

        EXIT

        LTORG

InitFreePoolTable
 [ FreePoolAddress > SysHeapStart
        &       0                               ; link: no more nodes on list
 |
        &       ZeroPage+SysHeapDANode
 ]
        &       ChangeDyn_FreePool
; [ HAL32
;        ! 0, "Sort out FreePoolInitTable"
;        &       -1
; |
        &       FreePoolAddress
; ]
        &       AP_FreePool
        &       0                               ; size will be updated later
        &       0                               ; max size is computed
        &       0                               ; no workspace needed
        &       0                               ; no handler needed
        &       FreePoolString                  ; title

InitSysHeapTable
 [ FreePoolAddress > SysHeapStart
        &       ZeroPage+FreePoolDANode         ; link -> free pool node, since FreePoolAddress > SysHeapStart
 |
        &       0
 ]
        &       ChangeDyn_SysHeap
        &       SysHeapStart
        &       AP_SysHeap
        &       32*1024-(SysHeapStart-SysHeapChunkAddress) ; size
        &       SysHeapMaxSize
        &       SysHeapStart                    ; workspace pointer -> base of heap
        &       DynAreaHandler_SysHeap          ; area handler
        &       SysHeapString                   ; title

InitAppSpaceTable
        &       0                               ; link: not on list
        &       ChangeDyn_AplSpace
        &       0                               ; base address
        &       AP_AppSpace
        &       0                               ; size will be set up later
        &       AplWorkMaxSize
        &       0                               ; no workspace needed
        &       0                               ; no handler needed
        &       AppSpaceString                  ; title

FreePoolString
        =       "Free pool", 0
AppSpaceString
        =       "Application space", 0
SysHeapString
        =       "System heap", 0
        ALIGN

;**************************************************************************
;
;       ChangeDynamicSWI - implement OS_ChangeDynamicArea (change the
;                          size of a dynamic area)
;
; in:   r0 = area number
;       r1 = size of change (in bytes, signed integer)
;
; out:  r0   preserved
;       r1 = actual amount moved (in bytes, unsigned integer)
;

  [ DA_Batman
;OR, a special call is allowed for internal use:
;
; in:   r0 = ChangeDyn_Batman (-3)
;       r1 = size of change (in bytes, signed integer)
;       r2 -> pseudo DANode for this call - base of DA will be base of fragment to map/unmap,
;             magnitude of r1 will give size of fragment, sign of r1 is +ve for map -ve for unmap
  ]

ChangeDynamicSWI ROUT
        Push    "r0, r2-r9, r10, lr"

        LDR     r10, =ZeroPage                  ; check we're not in an IRQ
        LDR     lr, [r10, #IRQsema]
        TEQ     lr, #0
        LDREQ   lr, [r10, #CDASemaphore]       ; now also check whether ChangeDynamicArea is already threaded
        TEQEQ   lr, #0
        LDREQB  lr, [r10, #VRAMRescue_control] ; also check that wimp has not locked Free Pool
        TSTEQ   lr, #VRRc_wimp_lock
        BNE     failure_IRQgoing
        STR     pc, [r10, #CDASemaphore]     ; store non-zero value in CDASemaphore, to indicate we're threaded

 [ DebugCDA2
        DLINE   "Entering OS_ChangeDynamicArea (new code)"
 ]

  [ DA_Batman
    ;check for special Batman call (which uses OS_ChangeDynamicArea to map or unmap a fragment of a sparse DA)
        CMP     r0, #ChangeDyn_Batcall
        MOVEQ   r10, r2                         ; Batman call passes a pseudo DANode in r2
        BEQ     CDA_handlechecked
  ]

        Push    "r1"
        MOV     r1, r0
 [ DebugCDA2
        DREG    r1, "Checking list for area number "
 ]

  [ DynArea_QuickHandles
        BL      QCheckAreaNumber                ; check area number is on list
  |
        BL      CheckAreaNumber                 ; check area number is on list
  ]

        Pull    "r1"
        BCC     failure_IRQgoingClearSemaphore

 [ DebugCDA2
        DLINE   "Found entry on list"
 ]

  [ DA_Batman
        ;a sparse area is not allowed to do an ordinary grow or shrink
        ;
        LDR     r11, [r10, #DANode_Flags]
        TST     r11, #DynAreaFlags_SparseMap
        BNE     failure_IRQgoingClearSemaphore
  ]

CDA_handlechecked

  [ DynArea_QuickHandles
        LDR     r11, =ZeroPage
        LDR     r11, [r11, #DynArea_ws]
        LDR     r5, DynArea_OD6Signature
        CMP     r0, #ChangeDyn_MaxArea
        BLS     daq_cda_od6done
        CMP     r0, #&FFFFFFF0                  ;don't count special DA numbers, such as ChangeDyn_AplSpace
        BHI     daq_cda_od6done
        TST     r5, #&80000000                  ;if set, disables resize signature for this call
        STREQ   r5, DynArea_OD6PrevSignature
        STREQ   r0, DynArea_OD6Handle
        ORREQ   r5, r5, #4
daq_cda_od6done
        BIC     r5, r5, #&80000000              ;clear any disable
        STR     r5, DynArea_OD6Signature
  ]

        LDR     r5, =ZeroPage
        LDR     r5, [r5, #Page_Size]            ; r5 = page size throughout
        SUB     r12, r5, #1                     ; r12 = page mask
        ADD     r1, r1, r12
        BICS    r1, r1, r12
        BEQ     IssueServiceMemoryMoved         ; zero pages! (r0 = area number, r1 = size change (0))
        BPL     AreaGrow

AreaShrink
        RSB     r1, r1, #0                      ; make size change positive
 [ DebugCDA2
        DREG    r0, "Shrinking area ", cc
        DREG    r1, " by "
 ]
        MOV     r11, r10                        ; source is area
        CMP     r0, #ChangeDyn_FreePool         ; if source is free pool
        ADREQ   r12, AppSpaceDANode             ; then dest is appspace
        ADRNE   r12, FreePoolDANode             ; else dest is free pool
      [ ZeroPage <> 0
        LDR     r2, =ZeroPage
        ADD     r12, r12, r2
      ]

        ASSERT  DANode_MaxSize = DANode_Size +4
        ADD     r2, r12, #DANode_Size
        LDMIA   r2, {r2, r3}
        SUB     lr, r3, r2                      ; lr = amount dest could grow

 [ ShrinkableDAs
        CMP     r1, lr
        MOVHI   r1, lr                          ; r1 = the most we want to move if we could
        BLHI    GenNotAllMovedError             ; but if not all we still want error
        SUB     lr, r5, #1                      ; lr = pagesize mask
        BICS    r1, r1, lr                      ; a pagesize multiple
        BEQ     IssueServiceMemoryMoved
 ]

 [ ShrinkableDAs
        LDR     r2, [r11, #DANode_Size]         ; amount src could shrink
        CMP     r2, r1
        BLCC    TryToShrinkShrinkables
        BCS     %FT15                           ; [we can now do it all]

; we can't move all that is required, so move smaller amount

        MOV     r1, r2                          ; move smaller amount
 |
        LDR     r2, [r11, #DANode_Size]         ; amount src could shrink
        CMP     r2, lr
        MOVCC   lr, r2                          ; lr = min(amount dest could grow, amount src could shrink)

        CMP     r1, lr
        BLS     %FT15

; we can't move all that is required, so move smaller amount

        MOV     r1, lr                          ; move smaller amount
 ]
        BL      GenNotAllMovedError
        SUB     lr, r5, #1                      ; lr = pagesize mask
        BICS    r1, r1, lr                      ; a pagesize multiple
        BEQ     IssueServiceMemoryMoved
15
      [ ZeroPage = 0
        CMP     r11, #AppSpaceDANode            ; if src <> appspace
        CMPNE   r12, #AppSpaceDANode            ; and dst <> appspace
      |
        LDR     lr, =ZeroPage+AppSpaceDANode
        CMP     r11, lr                         ; if src <> appspace
        CMPNE   r12, lr                         ; and dst <> appspace
      ]
        BNE     %FT17                           ; then don't call app
        Push    "r10"                           ; save -> to area we tried to shrink
        MOV     r10, r1
        BL      CheckAppSpace
        Pull    "r10"
        BVS     ChangeDynError
17
        BL      CallPreShrink
        BVS     ChangeDynError                  ; (r10 still points to area we tried to shrink)
        CMP     r2, r1                          ; can we move as much as we wanted?
        MOVCS   r2, r1                          ; if not, then move lesser amount (r2 = amount we're moving)
        BLCC    GenNotAllMovedError             ; store error, but continue

        TEQ     r2, #0                          ; if can't move any pages
        BEQ     NoMemoryMoved                   ; then exit, issuing Service_MemoryMoved

; Now move pages starting from end of area

        LDR     r0, [r11, #DANode_Base]
        LDR     r3, [r11, #DANode_Size]
        LDR     r6, [r11, #DANode_Flags]        ; r6 = src flags
        Push    "r3, r6"                        ; save src old size, src flags for later
        TST     r6, #DynAreaFlags_DoublyMapped  ; if src is doubly mapped
        MOVNE   r9, r3                          ; then set up offset from 1st copy to 2nd copy = old src size
        ADD     r0, r0, r3                      ; move r0 to point to after end of area (2nd copy)
        SUB     r3, r3, r2
        STR     r3, [r11, #DANode_Size]         ; store reduced source size

        LDR     r1, [r12, #DANode_Base]         ; this is free pool or app space, so it can't be doubly mapped!
        LDR     r3, [r12, #DANode_Size]
        ADD     r1, r1, r3                      ; r1 -> address of 1st extra page

        LDR     lr, =DynAreaFlags_AccessMask
        MOV     r4, r2
        LDR     r6, [r12, #DANode_Flags]        ; r6 = dst flags
        AND     r6, r6, lr
20
        SUB     r0, r0, r5                      ; pre-decrement source pointer
 [ DebugCDA2
        DREG    r0, "Moving page at ", cc
        DREG    r1, " to ", cc
        DREG    r6, " with PPL "
 ]
        BL      MovePageAtR0ToR1WithAccessR6
        ADD     r1, r1, r5
        SUBS    r4, r4, r5
        BNE     %BT20

        ADD     r3, r3, r2
        STR     r3, [r12, #DANode_Size]         ; store increased destination size
      [ ZeroPage = 0
        EORS    lr, r12, #AppSpaceDANode        ; check if dest = appspace (if so lr:=0)
        STREQ   r3, [lr, #MemLimit]             ; update memlimit if so
      |
        LDR     lr, =ZeroPage+AppSpaceDANode
        TEQ     r12, lr                         ; check if dest = appspace
        STREQ   r3, [lr, #MemLimit-AppSpaceDANode] ; update memlimit if so
      ]

        Pull    "r3, r6"                        ; restore src old size, src flags
        TST     r6, #DynAreaFlags_DoublyMapped  ; if src doubly mapped
        SUBNES  r4, r3, r2                      ; then set r4 = number of pages to shuffle up
        BEQ     %FT30                           ; [not doubly mapped, or no pages left, so skip]

        SUB     r0, r0, r3                      ; move r0 back to end of 1st copy of pages remaining
        ADD     r1, r0, r2                      ; r1 is end of where they're moving to (should be src base address!)
 [ 1 = 1
 |
        AND     r6, r6, #DynAreaFlags_AccessMask
 ]
        MOV     r9, #0                          ; no funny stuff while moving these pages
25
        SUB     r0, r0, r5
        SUB     r1, r1, r5
 [ 1 = 1
        BL      GetPageFlagsForR0IntoR6
 ]
        BL      MovePageAtR0ToR1WithAccessR6
        SUBS    r4, r4, r5
        BNE     %BT25

30
  [ EarlierReentrancyInDAShrink
        LDR     r4, =ZeroPage                   ; indicate no page block (and ptr to semaphore)
      [ ZeroPage = 0
        STR     r4, [r4, #CDASemaphore]         ; OK to reenter now (we've done the damage)
      |
        MOV     lr, #0
        STR     lr, [r4, #CDASemaphore]
      ]
  ]
        BL      CallPostShrink
        RSB     r1, r2, #0
        LDR     r0, [r11, #DANode_Number]       ; reload dynamic area number
        B       IssueServiceMemoryMoved

AreaGrow
 [ DebugCDA2
        DREG    r0, "Growing area ", cc
        DREG    r1, " by "
 ]
        MOV     r12, r10                        ; dest is area specified
        CMP     r0, #ChangeDyn_FreePool         ; if dest is free pool
        ADREQ   r11, AppSpaceDANode             ; then src is appspace
        ADRNE   r11, FreePoolDANode             ; else src is free pool (may later be free+apl)
      [ ZeroPage <> 0
        LDR     r2, =ZeroPage
        ADD     r11, r11, r2
      ]

        ASSERT  DANode_MaxSize = DANode_Size +4
        ADD     r2, r12, #DANode_Size
        LDMIA   r2, {r2, r3}
        SUB     lr, r3, r2                      ; lr = amount dest could grow

 [ DebugCDA2
        DREG    lr, "Dest could grow by "
 ]
      [ ZeroPage = 0
        LDR     r2, [r11, #DANode_Size]         ; amount src could shrink
        CMP     r11, #AppSpaceDANode            ; if appspace
      |
        LDR     r2, =ZeroPage+AppSpaceDANode
        CMP     r11, r2                         ; if appspace
        LDR     r2, [r11, #DANode_Size]         ; amount src could shrink
      ]
        SUBEQ   r2, r2, #&8000                  ; then can't take away last 32K (0..&7FFF)

 [ DebugCDA2
        DREG    r2, "Src could shrink by "
 ]

        CMP     r1, lr                          ; if enough room in dest
        CMPLS   r1, r2                          ; and enough space in src
        MOVLS   r3, r1                          ; then can do full amount
        BLS     %FT65                           ; so skip this bit

; we can't move all that is required
;
; if src = AplSpace then
;       (dest must be free pool)
;       move reduced amount
; else
;       (src must be free pool)
;       (dest <> AplSpace, cos that's a shrink!)
;       so check if adding aplspace would allow us to succeed
;       if it does then adjust registers, else give error
; endif
;

 [ DebugCDA2
        DLINE   "Can't move all required using just free pool"
 ]
      [ ZeroPage = 0
        CMP     r11, #AppSpaceDANode
      |
        Push    "lr"
        LDR     lr, =ZeroPage+AppSpaceDANode
        CMP     r11, lr
        Pull    "lr"
      ]
        BNE     %FT62
        MOV     r1, lr
        CMP     r1, r2
        MOVHI   r1, r2                          ; move min(max addable to dest, max removable from src)

 [ DebugCDA2
        DREG    r1, "Dest is free pool, moving reduced amount of "
 ]

61
        BL      GenNotAllMovedError
        SUB     lr, r5, #1                      ; lr = pagesize mask
        BICS    r1, r1, lr                      ; a pagesize multiple
        BEQ     IssueServiceMemoryMoved
        MOV     r3, r1
        B       %FT65

62

 [ ShrinkableDAs
; growing another area from free pool
; insert code here to check for shrinking shrinkable areas

        CMP     r1, lr                          ; if dest can't grow by this amount,
        BHI     %FT64                           ; we're definitely not doing anything

        CMP     r2, r1                          ; this should definitely set C=0 as required by TryToShrinkShrinkables
        Push    "lr"
        BLCC    TryToShrinkShrinkables
        Pull    "lr"
        MOVCS   r3, r1                          ; if succeeded set r3 to number of bytes to do
        BCS     %FT65                           ; and do it
64

; end of inserted code
 ]

        LDR     r4, =ZeroPage+AppSpaceDANode
        LDR     r6, [r4, #DANode_Size]          ; get current size of apl space
        SUB     r6, r6, #&8000                  ; can't take away 0-&7FFF
        ADD     r3, r2, r6                      ; add on to amount we could remove from free pool

 [ DebugCDA2
        DREG    r6, "Can get from app space an additional ", cc
        DREG    r3, " making a total of "
 ]

        CMP     r1, lr                          ; if not enough room in dest
        CMPLS   r1, r3                          ; or src still doesn't have enough
        MOVHI   r1, #0                          ; then don't move any
        BHI     %BT61                           ; and return error

        MOV     r3, r1                          ; amount actually doing

        TEQ     r2, #0                          ; else check to see if there was any at all in free pool
        LDREQ   r11, =ZeroPage+AppSpaceDANode   ; if not, then just take from aplspace
        MOVEQ   r7, r3                          ; and do all

        MOVNE   r11, #0                         ; else make src indicator reflect that we need both
        MOVNE   r7, r2                          ; but save amount we are taking from freepool

65

        Push    "r10"
      [ ZeroPage = 0
        CMP     r11, #AppSpaceDANode            ; if source = aplspace
      |
        LDR     r10, =ZeroPage+AppSpaceDANode
        CMP     r11, r10                        ; if source = aplspace
      ]
        MOV     r10, #0                         ; default value if apl space not involved
        RSBEQ   r10, r3, #0                     ; then make amount -ve
        CMP     r11, #0                         ; if source = free and apl
        SUBEQ   r10, r7, r3                     ; then make it -(amount removing from apl space)
        MOVNE   r7, r3                          ; else set up r7 to be total amount (wasn't set up above)

 [ DebugCDA2
        DREG    r3, "Amount actually moving into area = "
        DREG    r7, "Amount coming from 1st src area = "
 ]

        CMP     r10, #0                         ; if neither of the above then don't talk to app (CMP clears V)
        BLNE    CheckAppSpace                   ; else check app agrees
        Pull    "r10"
        BVS     ChangeDynError

; now split up grow into bite-size chunks, and call DoTheGrow to do each one

        Push    "r3"                            ; save original total amount
        TEQ     r11, #0                         ; if taking from both free + apl
        LDREQ   r11, =ZeroPage+FreePoolDANode   ; then start with free

        LDR     lr, [r12, #DANode_Flags]        ; could this area require particular physical pages at all?
        TST     lr, #DynAreaFlags_NeedsSpecificPages
        BNE     %FT70                           ; [yes it could, so do it in lumps]

        MOV     r1, #0                          ; no page block
        MOV     r2, r3, LSR #12                 ; number of pages to do
        BL      CallPreGrow
        LDRVS   r3, [sp]                        ; if error, haven't done any, so restore total as how much to do
        BVS     %FT95

        Push    "r3, r7"
        MOV     r2, r7, LSR #12
        BL      DoTheGrowNotSpecified
        Pull    "r3, r7"
        SUBS    r3, r3, r7                      ; subtract off what we just did

        MOVHI   r7, r3                          ; if not finished, then start 2nd half
        LDRHI   r11, =ZeroPage+AppSpaceDANode   ; which is app space
        MOVHI   r2, r7, LSR #12
        BLHI    DoTheGrowNotSpecified

        LDR     r3, [sp]                        ; restore total amount
        MOV     r1, #0                          ; indicate no page block (and ptr to semaphore)
      [ ZeroPage = 0
        STR     r1, [r1, #CDASemaphore]         ; OK to reenter now (we've done the damage)
      |
        LDR     r2, =ZeroPage
        STR     r1, [r2, #CDASemaphore]         ; OK to reenter now (we've done the damage)
      ]
        MOV     r2, r3, LSR #12
        BL      CallPostGrow
        BVS     %FT95
        B       %FT80

70
        Push    "r3, r7"
        CMP     r7, #PageBlockChunk             ; only do 1 area, so do min(r7,page)
        MOVHI   r7, #PageBlockChunk
        MOV     r2, r7, LSR #12                 ; number of entries to fill in in page block
        BL      DoTheGrow
        Pull    "r3, r7"
        BVS     %FT95
        CMP     r7, #PageBlockChunk             ; if 1st area is more than 1 page
        SUBHI   r3, r3, #PageBlockChunk         ; then reduce total
        SUBHI   r7, r7, #PageBlockChunk         ; and partial amounts by 1 page and do it again
        BHI     %BT70

        SUBS    r3, r3, r7                      ; subtract off what we just did
        MOVHI   r7, r3                          ; if not finished, then start 2nd half
        LDRHI   r11, =ZeroPage+AppSpaceDANode   ; which is app space
        BHI     %BT70                           ; and loop
80
        Pull    "r3"                            ; restore total amount

        MOV     r1, r3
        LDR     r0, [r12, #DANode_Number]       ; reload dynamic area number
        B       IssueServiceMemoryMoved

95
        Pull    "r1"                            ; restore total amount
        SUB     r1, r1, r3                      ; subtract off amount left, to leave done amount
        B       ChangeDynErrorSomeMoved

GenNotAllMovedError Entry "r0"
        ADRL    r0, ErrorBlock_ChDynamNotAllMoved
 [ International
        BL      TranslateError
 ]
        STR     r0, [sp, #2*4]          ; sp -> r0,lr, then stacked r0,r2-r9,r10,lr
        LDR     lr, [sp, #12*4]
        ORR     lr, lr, #V_bit
        STR     lr, [sp, #12*4]
        EXIT

        LTORG

ChangeDynError

; in:   r0 -> error
;       r10 -> area that we tried to shrink/grow

        MOV     r1, #0
ChangeDynErrorSomeMoved
        STR     r0, [sp]
        LDR     lr, [sp, #10*4]
        ORR     lr, lr, #V_bit
        STR     lr, [sp, #10*4]
        B       SomeMemoryMoved

NoMemoryMoved
        MOV     r1, #0                          ; nothing moved
SomeMemoryMoved
        LDR     r0, [r10, #DANode_Number]       ; reload area number

; and drop thru to...

IssueServiceMemoryMoved

; in:   r0 = area number that was shrunk/grown
;       r1 = amount moved (signed)
;
  [ DA_Batman
        CMP     r0, #ChangeDyn_Batcall
        BEQ     ISMM_BatCloak           ; cloaking device (no service issue)
  ]
        Push    "r1"
        MOV     r2, r0                  ; r2 = area number
        MOV     r0, r1                  ; amount moved (signed)
        MOV     r1, #Service_MemoryMoved
        BL      Issue_Service
        Pull    "r1"                    ; restore amount moved
  [ DA_Batman
ISMM_BatCloak
  ]
        TEQ     r1, #0
        RSBMI   r1, r1, #0              ; r1 on exit = unsigned amount

        LDR     r0, =ZeroPage
      [ ZeroPage = 0
        STR     r0, [r0, #CDASemaphore] ; clear CDASemaphore
      |
        MOV     lr, #0
        STR     lr, [r0, #CDASemaphore] ; clear CDASemaphore
      ]
        Pull    "r0, r2-r9, r10, lr"
        ExitSWIHandler

 [ ShrinkableDAs
; ***********************************************************************************
;
;       TryToShrinkShrinkables - Attempt to make more space by shrinking shrinkable areas if appropriate
;
; in:   r1 = total amount we wish to have in src area (already limited by max_size of destination area)
;       r2 = current size of src area
;       r11 -> src area node (we don't do anything unless this is the free pool)
;       r12 -> dst area node
;       C = 0
;
; out:  r2 = new size of src area
;       C=0 => failed to move as much as we wanted
;       C=1 => succeeded in moving as much as we wanted

TryToShrinkShrinkables Entry "r0,r1,r10"
        LDR     lr, [r11, #DANode_Number]
        TEQ     lr, #ChangeDyn_FreePool
        EXIT    NE                              ; if src <> free pool, exit with C, V flags intact

        LDR     r10, =ZeroPage+DAList
        ASSERT  DANode_Link = 0                 ; because DAList has only link
10
        LDR     r10, [r10, #DANode_Link]        ; and load next
        CMP     r10, #1                         ; any more nodes?
        EXIT    CC                              ; no, then no match
        TEQ     r10, r12                        ; check area <> dest
        LDRNE   lr, [r10, #DANode_Flags]        ; and area is shrinkable
        TSTNE   lr, #DynAreaFlags_Shrinkable
        BEQ     %BT10                           ; if not, try next area

        SUBS    r1, r1, r2                      ; r1 = amount we still need
        LDR     lr, [r10, #DANode_Size]         ; available size of this area
        CMP     lr, r1
        MOVCC   r1, lr                          ; min(amount we need, size of this area)
        RSB     r1, r1, #0                      ; make negative - it's a shrink
        LDR     r0, =ZeroPage
      [ ZeroPage = 0
        STR     r0, [r0, #CDASemaphore]         ; momentarily pretend we're not threaded
      |
        MOV     lr, #0
        STR     lr, [r0, #CDASemaphore]         ; momentarily pretend we're not threaded
      ]
        LDR     r0, [r10, #DANode_Number]
        SWI     XOS_ChangeDynamicArea
      [ ZeroPage = 0
        MOV     r0, #1
        STR     r0, [r0, #CDASemaphore-1]       ; we're threaded again
      |
        LDR     r0, =ZeroPage
        STR     r0, [r0, #CDASemaphore]         ; we're threaded again
      ]
        LDR     r1, [sp, #4]                    ; reload original r1
        LDR     r2, [r11, #DANode_Size]         ; get new size of src area
        CMP     r2, r1
        BCC     %BT10                           ; if still too small, loop
        EXIT                                    ; exit CS indicating success
 ]

; ***********************************************************************************
;
;       DoTheGrow - Do one chunk of growing, small enough to fit into the page block on the stack
;
; in:   r2 = number of entries to put in page block (for this chunk)
;       r5 = page size
;       r7 = amount taking from src area (in this chunk)
;       (r10 -> dest area)
;       r11 -> src area
;       r12 -> dest area
;
; out:  r0-r2,r4,r6-r9 may be corrupted
;       r3,r5,r10-r12 preserved
;
; Note: Removal is from one area only, the calling routine breaks the chunks up at free/app boundary.

; Temporary (stack frame) workspace used by this routine

                ^       0, sp
NumEntries      #       4                       ; Number of entries to do for this chunk
DestAddr        #       4                       ; Log addr of 1st page being added to dest
DestFlags       #       4                       ; Page flags for destination area
TotalAmount     #       4                       ; Total size of grow for this chunk (ie entry value of r3)
SavedPSR        #       4                       ; PSR before IRQs disabled
Offset1To2      #       4                       ; Offset from 1st to 2nd bank

DoTheGrowNotSpecifiedStackSize * :INDEX: @      ; amount of stack needed for 'not specified' version

PageBlock1      #       PageBlockSize           ; 1st page block, for original page numbers and phys. addrs
PageBlock2      #       PageBlockSize           ; 2nd page block, for new page numbers and phys. addrs

DoTheGrowStackSize *    :INDEX: @

DoTheGrow Entry "r3,r5,r10-r12", DoTheGrowStackSize

; First fill in the page block with -1 in the physical page number words

        STR     r2, NumEntries                  ; save number of entries for use later
        STR     r7, TotalAmount                 ; save amount growing by

        ADR     r1, PageBlock1                  ; point at 1st page block on stack
        ADD     lr, r2, r2, LSL #1              ; lr = number of words in page block
        ADD     lr, r1, lr, LSL #2              ; lr -> off end of page block
        MOV     r0, #-1
10
        STR     r0, [lr, #-12]!                 ; store -1, going backwards
        STR     r0, [lr, #PageBlockSize]        ; and put -1 in 2nd page block as well
        TEQ     lr, r1                          ; until the end
        BNE     %BT10

; Now call the pre-grow handler

        MOV     r3, r7
        BL      CallPreGrow
        EXIT    VS

; now check to see if particular pages are required

        LDR     lr, [r1]                        ; load page number in 1st entry
        CMP     lr, #-1                         ; is it -1?
        BNE     DoTheGrowPagesSpecified         ; if not, then jump to special code

; now move pages starting from end of area

        MOV     r2, r3                          ; amount moving
        LDR     r0, [r11, #DANode_Base]
        LDR     r3, [r11, #DANode_Size]
        ADD     r0, r0, r3                      ; move r0 to point to after end of area
        SUB     r3, r3, r2                      ; reduce by amount moving from area
        STR     r3, [r11, #DANode_Size]         ; store reduced source size
      [ ZeroPage = 0
        TEQ     r11, #AppSpaceDANode                    ; if just appspace
      |
        LDR     r1, =ZeroPage+AppSpaceDANode
        TEQ     r11, r1                                 ; if just appspace
      ]
        STREQ   r3, [r11, #MemLimit-AppSpaceDANode]     ; then store in memlimit

        LDR     r1, [r12, #DANode_Base]
        LDR     r3, [r12, #DANode_Size]

        LDR     r6, [r12, #DANode_Flags]        ; r6 = dst flags
        LDR     lr, =DynAreaFlags_AccessMask
        TST     r6, #DynAreaFlags_DoublyMapped  ; check if dst is doubly mapped
        AND     r6, r6, lr
        BEQ     %FT25                           ; [it's not, so skip all this]

; we must shunt all existing pages in dest area down

        MOVS    r4, r3                          ; amount to do
        BEQ     %FT20                           ; [none, so skip all this]
        Push    "r0, r1"
        SUB     r0, r1, r3                      ; src starts at start of 1st copy = start of 2nd - old size
        SUB     r1, r0, r2                      ; dst start = src start - amount of room needed
        MOV     r9, #0                          ; no funny business while moving these pages
15
        BL      MovePageAtR0ToR1WithAccessR6    ; move page
        ADD     r0, r0, r5                      ; advance src ptr
        ADD     r1, r1, r5                      ; advance dst ptr
        SUBS    r4, r4, r5                      ; one less page to move
        BNE     %BT15                           ; loop if more
        Pull    "r0, r1"                        ; restore original regs
20
        ADD     r9, r3, r2                      ; set up offset from 1st copy to 2nd copy (= new size)
25
        ADD     r1, r1, r3                      ; r1 -> address of 1st extra page
        MOV     r4, #0                          ; amount done so far
        MOV     r10, r2                         ; move amount to do into r10, as routine returns page number in r2
        ADR     r3, PageBlock1                  ; point at 1st entry we have to update
30
        SUB     r0, r0, r5                      ; pre-decrement source pointer
 [ DebugCDA2
        DREG    r0, "Moving page at ", cc
        DREG    r1, " to ", cc
        DREG    r6, " with PPL "
 ]
        BL      MovePageAtR0ToR1WithAccessR6ReturnPageNumber
        STR     r2, [r3], #12                   ; store page number and move on
        ADD     r1, r1, r5
        ADD     r4, r4, r5
        CMP     r4, r10                         ; have we done all of it?
        BNE     %BT30                           ; [no, so loop]
35
        LDR     r3, [r12, #DANode_Size]
        ADD     r3, r3, r10
        STR     r3, [r12, #DANode_Size]         ; store increased destination size

        MOV     r3, r10                         ; r3 = size of change
        LDR     r2, NumEntries                  ; restore number of entries in page block
        ADR     r1, PageBlock1                  ; point at page block 1 with page numbers filled in
        BL      CallPostGrow
        CLRV
        EXIT

37

; Come here if a required page is not available
; First we need to go back thru all the part of the page block we've already done,
; marking the pages as not being used after all

        ADR     r2, PageBlock1
38
        LDR     r4, [r1, #-12]!                 ; r4 = physical page number
        ADD     r4, r0, r4, LSL #3              ; point at cam entry
        LDMIA   r4, {r8, lr}
        BIC     lr, lr, #PageFlags_Required
        STMIA   r4, {r8, lr}
        TEQ     r1, r2
        BNE     %BT38

; since pre-grow handler exited without an error, we have to keep our promise
; to call the post-grow handler

        MOV     r3, #0                          ; no pages moved
        MOV     r2, #0                          ; no pages moved
        ADR     r1, PageBlock1                  ; not really relevant
        BL      CallPostGrow

        ADR     r0, ErrorBlock_CantGetPhysMem
 [ International
        BL      TranslateError
 ]
        SETV
        EXIT

        MakeErrorBlock  CantGetPhysMem

DoTheGrowPagesSpecified

; First check if any of the pages requested are unavailable
; At the same time as we're doing this, we fill in the log. and phys. addresses in the block

        LDR     r0, =ZeroPage
        LDR     r0, [r0, #CamEntriesPointer]
        LDR     r6, =L2PT
40
        LDR     r3, [r1], #12                   ; r4 = physical page number
        ADD     r4, r0, r3, LSL #3              ; point at cam entry
        LDMIA   r4, {r8, lr}                    ; r8 = log. addr, lr = PPL
        STR     r8, [r1, #4-12]                 ; store log. addr in page block
        STR     r8, [r1, #PageBlockSize+4-12]   ; and in 2nd page block

        TST     lr, #PageFlags_Unavailable :OR: PageFlags_Required ; if page in use by someone else, or by us, then return error
        BNE     %BT37
        ORR     lr, lr, #PageFlags_Required     ; set bit in flags to say page will be needed
        STR     lr, [r4, #4]                    ; and store back

; work out physical address direct from physical page number, NOT from logical address, since log addr may be Nowhere (multiply mapped)

        LDR     r4, =ZeroPage+PhysRamTable
42
        LDMIA   r4!, {r8, lr}                   ; load phys addr, size
        SUBS    r3, r3, lr, LSR #12             ; subtract off number of pages in this chunk
        BCS     %BT42

        ADD     r3, r3, lr, LSR #12             ; put back what could not be subtracted
        ADD     r8, r8, r3, LSL #12             ; and add onto base address
        STR     r8, [r1, #8-12]                 ; store physical address in page block

        SUBS    r2, r2, #1
        BNE     %BT40

; now issue Service_PagesUnsafe

        ADR     r2, PageBlock1                  ; r2 -> 1st page block
        LDR     r3, NumEntries                  ; r3 = number of entries in page block
        MOV     r1, #Service_PagesUnsafe
        BL      Issue_Service

; now move the pages

        LDR     r2, TotalAmount                 ; amount moving
        LDR     r0, [r11, #DANode_Base]
        LDR     r3, [r11, #DANode_Size]
        ADD     r0, r0, r3                      ; move r0 to point to after end of area
        SUB     r3, r3, r2                      ; reduce by amount moving from area
        STR     r3, [r11, #DANode_Size]         ; store reduced source size
      [ ZeroPage = 0
        TEQ     r11, #AppSpaceDANode                    ; if appspace
      |
        LDR     r1, =ZeroPage+AppSpaceDANode
        TEQ     r11, r1                                 ; if appspace
      ]
        STREQ   r3, [r11, #MemLimit-AppSpaceDANode]     ; then update memlimit

        LDR     r1, [r12, #DANode_Base]
        LDR     r3, [r12, #DANode_Size]

        LDR     r6, [r12, #DANode_Flags]        ; r6 = dst flags
        LDR     lr, =DynAreaFlags_AccessMask
        AND     r6, r6, lr
        ORR     r6, r6, #PageFlags_Unavailable  ; set unavailable bit
        STR     r6, DestFlags                   ; save for later
        TST     r6, #DynAreaFlags_DoublyMapped  ; check if dst is doubly mapped
        BEQ     %FT55                           ; [it's not, so skip all this, and r9 will be irrelevant]

; we must shunt all existing pages in dest area down

        MOVS    r4, r3                          ; amount to do
        BEQ     %FT50                           ; [none, so skip all this]
        Push    "r0, r1"
        SUB     r0, r1, r3                      ; src starts at start of 1st copy = start of 2nd - old size
        SUB     r1, r0, r2                      ; dst start = src start - amount of room needed
        MOV     r9, #0                          ; no funny business while moving these pages
45
        BL      MovePageAtR0ToR1WithAccessR6    ; move page
        ADD     r0, r0, r5                      ; advance src ptr
        ADD     r1, r1, r5                      ; advance dst ptr
        SUBS    r4, r4, r5                      ; one less page to move
        BNE     %BT45                           ; loop if more
        Pull    "r0, r1"                        ; restore original regs
50
        ADD     r9, r3, r2                      ; set up offset from 1st copy to 2nd copy (= new size)
55
        STR     r9, Offset1To2                  ; store offset 1st to 2nd copy
        ADD     r1, r1, r3                      ; r1 -> address of 1st extra page
        STR     r1, DestAddr
        ADR     r8, PageBlock1                  ; r8 -> position in 1st page block
        SUB     r2, r0, r2                      ; r2 = lowest address being removed from src
        LDR     r3, =ZeroPage
        LDR     r3, [r3, #CamEntriesPointer]
        MOV     r4, r0                          ; r4 is where we're at in allocating spare logical addresses
        LDR     r9, NumEntries                  ; number of entries still to do in 1st loop

; Now before we start, we must construct the second page block, with replacement page numbers

;        DLINE   "Start of 1st loop"

60
        LDR     r6, [r8], #12                   ; r6 = page number required
        LDR     r10, [r8, #8-12]                ; r10 = phys addr
        LDR     lr, [r3, r6, LSL #3]            ; lr = logical address for this page

;        DREG    r6, "Checking page ", cc
;        DREG    lr, "at address "

        CMP     lr, r2                          ; check if address is one being taken from src anyway
        BCC     %FT63
        CMP     lr, r0
        BCS     %FT63

;        DLINE   "Page is being taken away anyway"
        B       %FT68                           ; [page is being taken anyway, so use same page number + phys addr in 2nd block]

; page is not one being taken away, so put in 1st replacement page that isn't required by area

63
;        DLINE   "Page is not being taken, looking for replacement"

64
        SUB     r4, r4, r5                      ; go onto next page being taken from src
;        DREG    r4, "Considering address "

        LDR     lr, =L2PT
        LDR     lr, [lr, r4, LSR #10]           ; get L2PT entry (to get phys addr) for next free page
        MOV     r10, lr, LSR #12                ; r10 = phys addr >>> 12

; now convert phys addr to page number

        MOV     r6, #0
        LDR     r1, =ZeroPage+PhysRamTable
66
        LDMIA   r1!, {r7, lr}                   ; load phys addr, size
        SUB     r7, r10, r7, LSR #12            ; number of pages into this bank
        CMP     r7, lr, LSR #12                 ; if more than there are here,
        ADDCS   r6, r6, lr, LSR #12             ; then advance page number by number of pages in this bank
        BCS     %BT66                           ; and go onto next bank

        ADD     r6, r6, r7                      ; advance page number by no. of pages into this bank

        ADD     r1, r3, r6, LSL #3              ; r1 -> cam entry for this page
        LDR     r1, [r1, #4]                    ; get PPL for this page
        TST     r1, #PageFlags_Required         ; if this page is required for the operation
        BNE     %BT64                           ; then try next page

        MOV     r10, r10, LSL #12               ; make r10 proper phys addr
;        DREG    r6, "Using page number "
68
        STR     r6, [r8, #PageBlockSize-12]     ; store page number in 2nd block
        STR     r10, [r8, #PageBlockSize+8-12]  ; and store phys addr

        SUBS    r9, r9, #1                      ; one less entry to do
        BNE     %BT60

        MOV     r7, r3                          ; r7 -> camentries

; Now we can go onto the 2nd loop which actually moves the pages

        LDR     r1, DestAddr
        MOV     r4, #0                          ; amount done
        MOV     r0, r7                          ; point r0 at camentries
        LDR     r7, TotalAmount                 ; amount to do
        ADR     r8, PageBlock1
        LDR     r9, Offset1To2
70
        MRS     r14, CPSR
        STR     r14, SavedPSR                   ; save old PSR (note: stack must be flat when we do this!)

        Push    "r0-r4,r7-r12"                  ; save regs used during copy
        MOV     r1, #Service_ClaimFIQ
        BL      Issue_Service

        WritePSRc I_bit+SVC_mode, r6            ; disable IRQs round here (we don't want interrupt code to update
                                                ; the old mapping behind us while we're trying to copy it)

        LDR     r6, [r8, #0]                    ; r6 = page number required
        LDR     lr, [r8, #PageBlockSize+0]      ; lr = page number of replacement page
        TEQ     r6, lr                          ; if the same
        Pull    "r0-r4,r7-r12", EQ              ; then restore registers
        BEQ     %FT76                           ; and skip copy and first page move

        ;mjs
        ; - if the old page is currently mapped in, copy normally
        ; - if the old page is not mapped in, copy via temporary mapping
        ; The old scheme, always copying from other mapping, had interrupt cache coherency hole, at least for
        ; ARM with writeback cache (bug in 3.7, fixed in Ursula, then lost)

        LDR     r0, [r0, lr, LSL #3]            ; r0 = log. address for replacement page (NB use logical address to write to, for cache consistency)

        LDR     r6, [r8, #4]                    ;logical address of src page
        LDR     r3, =Nowhere
        TEQ     r6, r3                          ;will be 'Nowhere' if not mapped in
        BNE     %FT71

        ASSERT  HAL
        SUB     sp, sp, #4                      ; for oldp
        Push    "r0,r1"
        MOV     r0, #0
        LDR     r1, [r8, #8]                    ; r1 = physical address of src for copy
        ADD     r2, sp, #8                      ; must use physical address, as page may be mapped to nowhere along with others
        BL      RISCOS_AccessPhysicalAddress
        MOV     r6, r0                          ; r6 = logical address of src for copy
        Pull    "r0,r1"

71
        ADD     lr, r6, r5                      ; lr = end src address
72
        LDMIA   r6!, {r2, r3, r4, r7, r9, r10, r11, r12}
        STMIA   r0!, {r2, r3, r4, r7, r9, r10, r11, r12}
        TEQ     r6, lr
        BNE     %BT72

        LDR     r0, [r8, #4]                    ;logical address of src page
        LDR     r3, =Nowhere
        TEQ     r0, r3

        Pull    "r0", EQ                        ; oldp
        BLEQ    RISCOS_ReleasePhysicalAddress

; now check if page we're replacing is in L2PT, and if so then adjust L1PT entries (4 of these)

        LDR     r6, [r8, #4]                    ; look at logical address of page being replaced
        SUBS    r6, r6, #L2PT
        BCC     %FT74                           ; address is below L2PT
        CMP     r6, #4*1024*1024
        BCS     %FT74                           ; address is above L2PT

        LDR     r2, =L1PT
        ADD     r2, r2, r6, LSR #(12-4)         ; address in L1 of 4 consecutive words to update
        LDR     r3, [r2]                        ; load 1st word, to get AP etc bits
        MOV     r3, r3, LSL #(31-9)             ; junk other bits
        LDR     r4, [r8, #PageBlockSize+8]      ; load new physical address for page
        ORR     r3, r4, r3, LSR #(31-9)         ; and merge with AP etc bits
        STR     r3, [r2], #4
        ADD     r3, r3, #&400
        STR     r3, [r2], #4
        ADD     r3, r3, #&400
        STR     r3, [r2], #4
        ADD     r3, r3, #&400
        STR     r3, [r2], #4
74
        Pull    "r0-r4,r7-r12"                  ; restore registers

        ; mjs
        ; OK, what we are about to do is:
        ;   1) move replacement page in (to replace needed page)
        ;   2) move needed page to required destination
        ; This order means that we don't leave a temporary hole at the logical address we're substituting,
        ; which is vital at least in the horrendous case where the logical page is itself used for L2PT.
        ; However, this means there is a potential temporary degeneracy in the caches, two physical pages
        ; having been seen at the same logical address (undefined behaviour).
        ; So, to be safe, we do a MMUChangingEntry first, for the logical page, which will clean/invalidate
        ; caches and invalidate TLBs, to avoid degeneracy. This is slight overkill in some cases, but vital
        ; to avoid serious grief in the awkward cases. Fortunately, these page substitutions are relatively
        ; rare, so performance is not critical.

        LDR     lr, =ZeroPage
        LDR     lr, [lr, #CamEntriesPointer]    ; lr -> soft cam map
        ADD     lr, lr, #4                      ; point at PPLs, not addresses
        LDR     r2, [r8, #0]                    ; need to get PPL for page being replaced
        LDR     r11, [lr, r2, LSL #3]
        BIC     r11, r11, #PageFlags_Required   ; knock off bits that indicate that it was a required page

        ADD     lr, r8, #PageBlockSize
        LDMIA   lr, {r2, r3}                    ; get page number, logical address

        Push    "r0, r4"
        LDR     r4, =Nowhere                    ; There's no point in cleaning the nowhere page, and on some architectures it'll even trigger an abort handler due to the lack of mapping
        MOV     r0, r3
        TEQ     r3, r4
        LDRNE   r4, =ZeroPage
        ARMop   MMU_ChangingEntry,NE,,r4
        Pull    "r0, r4"

        BL      Call_CAM_Mapping                ; move replacement page in
76
        LDR     r2, [r8, #0]
        MOV     r3, r1
        LDR     r11, DestFlags
        BL      Call_CAM_Mapping                ; move needed page to destination

        LDR     lr, SavedPSR
        MSR     CPSR_cf, lr

        Push    "r1"
        MOV     r1, #Service_ReleaseFIQ
        BL      Issue_Service
        Pull    "r1"

        ADD     r1, r1, r5                      ; advance dest ptr
        ADD     r4, r4, r5                      ; increment amount done
        ADD     r8, r8, #12                     ; advance page block ptr
        CMP     r4, r7                          ; have we done all?
        BNE     %BT70                           ; [no, so loop]

        LDR     r3, [r12, #DANode_Size]
        ADD     r3, r3, r7
        STR     r3, [r12, #DANode_Size]         ; store increased destination size

; now issue Service_PagesSafe

        LDR     r2, NumEntries
        ADR     r3, PageBlock1
        ADR     r4, PageBlock2
        MOV     r1, #Service_PagesSafe
        BL      Issue_Service

; now call Post_Grow handler

        LDR     r3, TotalAmount                 ; size of grow
        LDR     r2, NumEntries                  ; restore number of entries in page block
        ADR     r1, PageBlock1                  ; point at page block 1 with page numbers filled in
        BL      CallPostGrow
        CLRV
        EXIT

        LTORG

; ***********************************************************************************
;
;       DoTheGrowNotSpecified - Do one chunk of growing, with no page block
;                               But don't call pre-grow or post-grow either
;
; in:   r2 = number of pages to do (in this chunk)
;       r5 = page size
;       r7 = amount taking from src area (in this chunk)
;       (r10 -> dest area)
;       r11 -> src area
;       r12 -> dest area
;
; out:  r0-r2,r4,r6-r9 may be corrupted
;       r3,r5,r10-r12 preserved
;
; Note: Removal is from one area only, the calling routine breaks the chunk at free/app boundary.


DoTheGrowNotSpecified Entry "r3,r5,r10-r12", DoTheGrowNotSpecifiedStackSize

        STR     r2, NumEntries                  ; save number of entries for use later
        STR     r7, TotalAmount                 ; save amount growing by

; now move pages starting from end of area

        MOV     r2, r7                          ; amount moving
        LDR     r0, [r11, #DANode_Base]
        LDR     r3, [r11, #DANode_Size]
        ADD     r0, r0, r3                      ; move r0 to point to after end of area
        SUB     r3, r3, r2                      ; reduce by amount moving from area
        STR     r3, [r11, #DANode_Size]         ; store reduced source size
      [ ZeroPage = 0
        TEQ     r11, #AppSpaceDANode                    ; if just appspace
      |
        LDR     r1, =ZeroPage+AppSpaceDANode
        TEQ     r1, r11                                 ; if just appspace
      ]
        STREQ   r3, [r11, #MemLimit-AppSpaceDANode]     ; then store in memlimit

        LDR     r1, [r12, #DANode_Base]
        LDR     r3, [r12, #DANode_Size]

        LDR     r6, [r12, #DANode_Flags]        ; r6 = dst flags
        LDR     lr, =DynAreaFlags_AccessMask
        AND     r6, r6, lr
        TST     r6, #DynAreaFlags_DoublyMapped  ; check if dst is doubly mapped
        BEQ     %FT25                           ; [it's not, so skip all this]

; we must shunt all existing pages in dest area down

        MOVS    r4, r3                          ; amount to do
        BEQ     %FT20                           ; [none, so skip all this]
        Push    "r0, r1"
        SUB     r0, r1, r3                      ; src starts at start of 1st copy = start of 2nd - old size
        SUB     r1, r0, r2                      ; dst start = src start - amount of room needed
        MOV     r9, #0                          ; no funny business while moving these pages
15
        BL      MovePageAtR0ToR1WithAccessR6    ; move page
        ADD     r0, r0, r5                      ; advance src ptr
        ADD     r1, r1, r5                      ; advance dst ptr
        SUBS    r4, r4, r5                      ; one less page to move
        BNE     %BT15                           ; loop if more
        Pull    "r0, r1"                        ; restore original regs
20
        ADD     r9, r3, r2                      ; set up offset from 1st copy to 2nd copy (= new size)
25
        ADD     r1, r1, r3                      ; r1 -> address of 1st extra page
        MOV     r4, #0                          ; amount done so far
        MOV     r10, r2                         ; move amount to do into r10
30
        SUB     r0, r0, r5                      ; pre-decrement source pointer
 [ DebugCDA2
        DREG    r0, "Moving page at ", cc
        DREG    r1, " to ", cc
        DREG    r6, " with PPL "
 ]
        BL      MovePageAtR0ToR1WithAccessR6
        ADD     r1, r1, r5
        ADD     r4, r4, r5
        CMP     r4, r10                         ; have we done all of it?
        BNE     %BT30                           ; [no, so loop]
35
        LDR     r3, [r12, #DANode_Size]
        ADD     r3, r3, r10
        STR     r3, [r12, #DANode_Size]         ; store increased destination size

        CLRV
        EXIT

; ***********************************************************************************
;
;       CheckAppSpace - If appspace involved in transfer, issue Service or UpCall
;
;       Internal routine, called by OS_ChangeDynamicArea
;
; in:   r0 = area number passed in to ChangeDyn
;       r10 = size of change (signed)
;       r11 -> node for src
;       r12 -> node for dest
;
; out:  If appspace not involved, or application said it was OK, then
;         V=0
;         All registers preserved
;       else
;         V=1
;         r0 -> error
;         All other registers preserved
;       endif
;

CheckAppSpace Entry "r0-r3"
        LDR     r2, =ZeroPage
        LDR     r3, [r2, #AplWorkSize]
        LDR     r2, [r2, #Curr_Active_Object]
        CMP     r2, r3                          ; check if CAO outside application space
        BHI     %FT20                           ; [it is so issue Service not UpCall]

; CAO in application space, so issue UpCall to check it's OK

        MOV     r0, #UpCall_MovingMemory :AND: &FF
        ORR     r0, r0, #UpCall_MovingMemory :AND: &FFFFFF00
        MOVS    r1, r10
        RSBMI   r1, r1, #0                      ; r1 passed in is always +ve (probably a bug, but should be compat.)

        SWI     XOS_UpCall
        CMP     r0, #UpCall_Claimed             ; if upcall claimed
    [  ChocolateAMB
        BNE     %FT05
        BL      Do_AMB_MakeUnsparse             ; then ok to move memory, so undo laziness and exit
        CLRV
        EXIT
    |
        EXIT    EQ                              ; then OK to move memory, so exit (V=0 from CMP)
    ]

05
        ADR     r0, ErrorBlock_ChDynamCAO
10
 [ International
        BL      TranslateError
 ]
        STR     r0, [sp]
        SETV
        EXIT

; IF service call claimed Then Error AplWSpaceInUse

20
        MOV     r0, r10                         ; amount removing from aplspace
        MOV     r1, #Service_Memory
        BL      Issue_Service
        CMP     r1, #Service_Serviced
        ADREQ   r0, ErrorBlock_AplWSpaceInUse   ; if service claimed, then return error
        BEQ     %BT10
  [ ChocolateAMB
        BL      Do_AMB_MakeUnsparse             ; undo laziness
  ]
        CLRV                                    ; else OK
        EXIT

    [ ChocolateAMB
Do_AMB_MakeUnsparse ROUT
        Push    "r0, lr"
        MOVS    r0, r10
        RSBMI   r0, r0, #0
        BL      AMB_MakeUnsparse  ;shrinking AppSpace, so make sure unsparse over area of shrink
        Pull    "r0, pc"
    ] ;ChocolateAMB

        MakeErrorBlock AplWSpaceInUse
        MakeErrorBlock ChDynamCAO

; ***********************************************************************************
;
;       CallPreShrink - Call pre-shrink routine
;
; in:   r1 = amount shrinking by (+ve)
;       r5 = page size
;       r11 -> node for area being shrunk
;
; out:  If handler exits VC, then r2 = no. of bytes area can shrink by
;       else r0 -> error block or 0 for generic error, and r2=0
;

CallPreShrink Entry "r0,r3,r4, r12"
        LDR     r0, [r11, #DANode_Handler]              ; check if no handler
        CMP     r0, #0                                  ; if none (V=0)
        EXIT    EQ                                      ; then exit

        MOV     r0, #DAHandler_PreShrink                ; r0 = reason code
        MOV     r3, r1                                  ; r3 = amount shrinking by
        LDR     r4, [r11, #DANode_Size]                 ; r4 = current size
        ASSERT  DANode_Handler = DANode_Workspace +4
        ADD     r12, r11, #DANode_Workspace
        MOV     lr, pc
        LDMIA   r12, {r12, pc}                          ; load workspace pointer and jump to handler

; shrink amount returned by handler may not be page multiple (according to spec),
; so we'd better make it so.

        SUB     lr, r5, #1
        BIC     r2, r3, lr                              ; make page multiple and move into r2
        EXIT    VC
        TEQ     r0, #0                                  ; if generic error returned
        ADREQL  r0, ErrorBlock_ChDynamNotAllMoved       ; then substitute real error message
 [ International
        BLEQ    TranslateError
 ]
        STR     r0, [sp]
        SETV
        EXIT

; ***********************************************************************************
;
;       CallPostShrink - Call post-shrink routine
;
; in:   r2 = amount shrinking by (+ve)
;       r5 = page size
;       r11 -> node for area being shrunk
;
; out:  All registers preserved
;

CallPostShrink Entry "r0,r3,r4, r12"
        LDR     r0, [r11, #DANode_Handler]              ; check if no handler
        CMP     r0, #0                                  ; if none (V=0)
        EXIT    EQ                                      ; then exit

        MOV     r0, #DAHandler_PostShrink               ; r0 = reason code
        MOV     r3, r2                                  ; r3 = amount shrunk by
        LDR     r4, [r11, #DANode_Size]                 ; r4 = new size
        ASSERT  DANode_Handler = DANode_Workspace +4
        ADD     r12, r11, #DANode_Workspace
        MOV     lr, pc
        LDMIA   r12, {r12, pc}                          ; load workspace pointer and jump to handler

        EXIT

; ***********************************************************************************
;
;       CallPreGrow - Call pre-grow routine
;
; in:   Eventually r1 -> page block (on stack)
;                  r2 = number of entries in block
;       but for now these are both undefined
;       r3 = amount area is growing by
;       r5 = page size
;       r12 -> node for area being grown
;
; out:  If can't grow, then
;         r0 -> error
;         V=1
;       else
;         page block may be updated with page numbers (but not yet!)
;         All registers preserved
;         V=0
;       endif
;

CallPreGrow Entry "r0,r4, r12"
        LDR     r0, [r12, #DANode_Handler]              ; check if no handler
        CMP     r0, #0                                  ; if none (V=0)
        EXIT    EQ                                      ; then exit

        MOV     r0, #DAHandler_PreGrow                  ; r0 = reason code
        LDR     r4, [r12, #DANode_Size]                 ; r4 = current size
        ASSERT  DANode_Handler = DANode_Workspace +4
        ADD     r12, r12, #DANode_Workspace
        MOV     lr, pc
        LDMIA   r12, {r12, pc}                          ; load workspace pointer and jump to handler
        EXIT    VC                                      ; if no error then exit

        TEQ     r0, #0                                  ; if generic error returned
        ADREQL  r0, ErrorBlock_ChDynamNotAllMoved       ; then substitute real error message
 [ International
        BLEQ    TranslateError
 ]
        STR     r0, [sp]
        SETV
        EXIT

; ***********************************************************************************
;
;       CallPostGrow - Call post-grow routine
;
; in:   Eventually, r1 -> page block with actual pages put in
;                   r2 = number of entries in block
;       r3 = size of change
;       r5 = page size
;       r12 -> node for area being grown
;
; out:  All registers preserved
;

CallPostGrow Entry "r0,r3,r4, r12"
        LDR     r0, [r12, #DANode_Handler]              ; check if no handler
        CMP     r0, #0                                  ; if none (V=0)
        EXIT    EQ                                      ; then exit

        MOV     r0, #DAHandler_PostGrow                 ; r0 = reason code
        LDR     r4, [r12, #DANode_Size]                 ; r4 = new size
        ASSERT  DANode_Handler = DANode_Workspace +4
        ADD     r12, r12, #DANode_Workspace
        MOV     lr, pc
        LDMIA   r12, {r12, pc}                          ; load workspace pointer and jump to handler
        EXIT

 [ ShrinkableDAs
; ***********************************************************************************
;
;       CallTestShrink - Call test-shrink routine
;
; in:   r11 -> area node
;
; out:  If handler exits VC, then r3 = no. of bytes area can shrink by
;       else r0 -> error block or 0 for generic error, and r3=0
;

CallTestShrink Entry "r0,r4,r5, r12"
        LDR     r0, [r11, #DANode_Handler]              ; check if no handler
        CMP     r0, #0                                  ; if none (V=0)
        EXIT    EQ                                      ; then exit

        MOV     r0, #DAHandler_TestShrink               ; r0 = reason code
        LDR     r4, [r11, #DANode_Size]                 ; r4 = current size
        LDR     r5, =ZeroPage
        LDR     r5, [r5, #Page_Size]                    ; set r5 = page size
        ASSERT  DANode_Handler = DANode_Workspace +4
        ADD     r12, r11, #DANode_Workspace
        MOV     lr, pc
        LDMIA   r12, {r12, pc}                          ; load workspace pointer and jump to handler

; shrink amount returned by handler may not be page multiple (according to spec),
; so we'd better make it so.

        SUBVC   lr, r5, #1
        BICVC   r3, r3, lr                              ; make page multiple
        EXIT    VC
        TEQ     r0, #0                                  ; if generic error returned
        ADREQL  r0, ErrorBlock_ChDynamNotAllMoved       ; then substitute real error message
 [ International
        BLEQ    TranslateError
 ]
        STR     r0, [sp]
        MOV     r3, #0                                  ; indicate no shrink possible
        SETV
        EXIT
 ]

; ***********************************************************************************
;
;       MovePageAtR0ToR1WithAccessR6
;
;       Internal routine, called by OS_ChangeDynamicArea
;
; in:   r0 = logical address where page is now
;       r1 = logical address it should be moved to
;       r6 = area flags (which contain access privileges, and cacheable/bufferable bits)
;
; out:  All registers preserved
;

MovePageAtR0ToR1WithAccessR6 Entry "r2-r5,r11"
        MOV     r3, r1
        MOV     r11, r6
        BL      MoveCAMatR0toR3         ; use old internal routine for now
        EXIT

; Same as above, but returns with r2 = page number of page that moved

MovePageAtR0ToR1WithAccessR6ReturnPageNumber Entry "r3-r5,r11"
        MOV     r3, r1
        MOV     r11, r6
        BL      MoveCAMatR0toR3         ; use old internal routine for now
        EXIT

; ***********************************************************************************
;
;       DynAreaHandler_SysHeap - Dynamic area handler for system heap
;       DynAreaHandler_RMA     - Dynamic area handler for RMA
;
; in:   r0 = reason code (0=>pre-grow, 1=>post-grow, 2=>pre-shrink, 3=>post-shrink)
;       r12 -> base of area
;

DynAreaHandler_SysHeap
DynAreaHandler_RMA ROUT
        CMP     r0, #4
        ADDCC   pc, pc, r0, LSL #2
        B       UnknownHandlerError
        B       PreGrow_Heap
        B       PostGrow_Heap
        B       PreShrink_Heap
        B       PostShrink_Heap

PostGrow_Heap
PostShrink_Heap
        STR     r4, [r12, #:INDEX:hpdend] ; store new size

; and drop thru to...

PreGrow_Heap
        CLRV                            ; don't need to do anything here
        MOV     pc, lr                  ; so just exit

PreShrink_Heap
        Push    "r0, lr"
        PHPSEI                          ; disable IRQs round this bit
        LDR     r0, [r12, #:INDEX:hpdbase]      ; get minimum size
        SUB     r0, r4, r0              ; r0 = current-minimum = max shrink
        CMP     r3, r0                  ; if requested shrink > max
        MOVHI   r3, r0                  ; then limit it
        SUB     r0, r5, #1              ; r0 = page mask
        BIC     r3, r3, r0              ; round size change down to page multiple
        SUB     r0, r4, r3              ; area size after shrink
        STR     r0, [r12, #:INDEX:hpdend] ; update size

        PLP                             ; restore IRQ status
        CLRV
        Pull    "r0, pc"

AreaName_RMA
        =       "Module area", 0
  [ LongCommandLines :LAND: (:LNOT: HAL)
AreaName_Kbuffs
        =       "Kernel buffers", 0
  ]
        ALIGN


UnknownHandlerError
        Push    "lr"
        ADRL    r0, ErrorBlock_UnknownAreaHandler
  [ International
        BL      TranslateError
  ]
        SETV
        Pull    "pc"

DynAreaHandler_Sprites
        CMP     r0, #4
        ADDCC   pc, pc, r0, LSL #2
        B       UnknownHandlerError
        B       PreGrow_Sprite
        B       PostGrow_Sprite
        B       PreShrink_Sprite
        B       PostShrink_Sprite

PostGrow_Sprite
PostShrink_Sprite Entry "r0"

; in - r3 = size change (+ve), r4 = new size, r5 = page size

        LDR     lr, =ZeroPage+VduDriverWorkSpace
        TEQ     r4, #0                  ; if new size = 0
        STREQ   r4, [lr, #SpAreaStart]  ; then set area ptr to zero
        STRNE   r12, [lr, #SpAreaStart] ; else store base address

        LDR     r0, =ZeroPage
        LDR     lr, [r0, #SpriteSize]   ; load old size
        STR     r4, [r0, #SpriteSize]   ; and store new size
        BEQ     %FT10                   ; if new size is zero, don't try to update header

        STR     r4, [r12, #saEnd]       ; store new size in header
        TEQ     lr, #0                  ; if old size was zero
        STREQ   lr, [r12, #saNumber]    ; then initialise header (no. of sprites = 0)
        MOVEQ   lr, #saExten
        STREQ   lr, [r12, #saFirst]     ; ptr to first sprite -> after header
        STREQ   lr, [r12, #saFree]      ; ptr to first free byte -> after header

10
        CLRV                            ; don't need to do anything here
        EXIT                            ; so just exit

PreGrow_Sprite
        CLRV                            ; don't need to do anything here
        MOV     pc, lr                  ; so just exit

PreShrink_Sprite Entry "r0"
        TEQ     r4, #0                  ; if current size is zero
        BEQ     %FT10                   ; then any shrink is OK (shouldn't happen)

        LDR     r0, [r12, #saFree]      ; get used amount
        TEQ     r0, #saExten            ; if only header used,
        MOVEQ   r0, #0                  ; then none really in use

        SUB     r0, r4, r0              ; r0 = current-minimum = max shrink
        CMP     r3, r0                  ; if requested shrink > max
        MOVHI   r3, r0                  ; then limit it
        SUB     r0, r5, #1              ; r0 = page mask
        BIC     r3, r3, r0              ; round size change down to page multiple
10
        CLRV
        EXIT

AreaName_SpriteArea
        =       "System sprites", 0
        ALIGN


DynAreaHandler_RAMDisc
        CMP     r0, #4
        ADDCC   pc, pc, r0, LSL #2
        B       UnknownHandlerError
        B       PreGrow_RAMDisc
        B       PostGrow_RAMDisc
        B       PreShrink_RAMDisc
        B       PostShrink_RAMDisc

PostGrow_RAMDisc
PostShrink_RAMDisc Entry "r0-r6"

; in - r3 = size change (+ve), r4 = new size, r5 = page size
; but we don't really care about any of these

; The only thing we have to do here is ReInit RAMFS, but NOT if
; a) no modules are initialised yet (eg when we're created), or
; b) RAMFS has been unplugged

        LDR     r0, =ZeroPage
        LDR     r0, [r0, #Module_List]
        TEQ     r0, #0                  ; any modules yet?
        BEQ     %FT90                   ; no, then don't do anything

        MOV     r0, #ModHandReason_EnumerateROM_Modules
        MOV     r1, #0
        MOV     r2, #-1                 ; enumerate ROM modules looking for RAMFS
10
        SWI     XOS_Module
        BVS     %FT50                   ; no more modules, so it can't be unplugged
        ADR     r5, ramfsname
20
        LDRB    r6, [r3], #1            ; get char from returned module name
        CMP     r6, #" "                ; if a terminator then we have a match
        BLS     %FT30                   ; so check for unplugged
        LowerCase r6, lr                ; else force char to lower case
        LDRB    lr, [r5], #1            ; get char from "ramfs" string
        CMP     lr, r6                  ; and if matches
        BEQ     %BT20                   ; then try next char
        B       %BT10                   ; else try next module

30
        CMP     r4, #-1                 ; is module unplugged?
        BEQ     %FT90                   ; if so, then mustn't reinit it
50
        MOV     r0, #ModHandReason_ReInit ; reinit module
        ADR     r1, ramfsname
        SWI     XOS_Module              ; ignore any errors from this
90
        CLRV
        EXIT

PreGrow_RAMDisc
PreShrink_RAMDisc Entry "r0-r5"
        LDR     r0, =ZeroPage
        LDR     r0, [r0, #Module_List]  ; first check if any modules going
        TEQ     r0, #0
        BEQ     %FT90                   ; if not, don't look at filing system

        MOV     r0, #5
        ADR     r1, ramcolondollardotstar
        SWI     XOS_File
        CMPVC   r0, #0
        BVS     %FT90                   ; if no RAMFS then change OK
        BEQ     %FT90                   ; or if no files, then change OK
        ADR     r0, ErrorBlock_RAMFsUnchangeable
 [ International
        BL      TranslateError
 ]
        STR     r0, [sp]
        SETV
        EXIT

90
        CLRV
        EXIT

        MakeErrorBlock  RAMFsUnchangeable

AreaName_RAMDisc
        =       "RAM disc", 0
ramcolondollardotstar
        =       "ram:$.*", 0
ramfsname
        =       "ramfs", 0
        ALIGN


DynAreaHandler_FontArea
        CMP     r0, #4
        ADDCC   pc, pc, r0, LSL #2
        B       UnknownHandlerError
        B       PreGrow_FontArea
        B       PostGrow_FontArea
        B       PreShrink_FontArea
        B       PostShrink_FontArea

PostGrow_FontArea Entry "r0-r2"

; in - r3 = size change (+ve), r4 = new size, r5 = page size

        LDR     r1, =ZeroPage
        LDR     r1, [r1, #Module_List]  ; any modules active?
        TEQ     r1, #0
        MOVNE   r1, r4                  ; there are, so inform font manager of size change
        SWINE   XFont_ChangeArea
        CLRV
        EXIT

PostShrink_FontArea
PreGrow_FontArea
        CLRV                            ; don't need to do anything here
        MOV     pc, lr                  ; so just exit

PreShrink_FontArea Entry "r0-r2"
        MOV     r1, #-1                 ; ask font manager for minimum size of font area
        MOV     r2, #0                  ; default value if no font manager
        SWI     XFont_ChangeArea        ; out: r2 = minimum size

        SUB     r0, r4, r2              ; r0 = current-minimum = max shrink
        CMP     r3, r0                  ; if requested shrink > max
        MOVHI   r3, r0                  ; then limit it
        SUB     r0, r5, #1              ; r0 = page mask
        BIC     r3, r3, r0              ; round size change down to page multiple

        SUB     r1, r4, r3              ; r1 = new size
        SWI     XFont_ChangeArea        ; tell font manager to reduce usage

        CLRV
        EXIT

AreaName_FontArea
        =       "Font cache", 0
        ALIGN


; **** New screen stuff ****
;
;
; This source collects together all the new routines needed to make
; the screen into a new dynamic area.
;
; It has the following dependencies elsewhere in the kernel before
; it can be expected to work:
;
; * Definition of AP_Screen in ChangeDyn needs doubly_mapped and
;   name_is_token bits set
; * name_is_token handling needs adding (or scrap the bit designation)
; * Call to CreateNewScreenArea from NewReset to create area
; * Tim says doubly-mapped areas are broken - this must be fixed first
; * Old CDA routine may be retired, since screen is its last client
; * Has Tim completed the rest of this work?
;
; Once these routines work, they should be grafted into appropriate
; places in the kernel sources
;
; This source is not intended for stand-alone assembly: it should be
; plumbed into the kernel source build
;
; Version history - remove this once integrated with kernel sources
;
; Vsn  Date      Who  What
; ---  --------  ---  ----------------------------------------------
; 000  23/08/93  amg  Written
; 001  24/08/93  amg  Fixes and changes following review by TMD
; 002  03/09/93  tmd  Updated to work!

; *********************************************************************
; Create a new style dynamic area for the screen
; *********************************************************************

; Entry requirements
; none

AreaName_Screen
        =       "Screen memory",0               ;needs replacing with message token
        ALIGN

; *********************************************************************
; Handler despatch routine for screen dynamic area
; *********************************************************************

DynAreaHandler_Screen                           ;despatch routine for pre/post grow/shrink handlers
        CMP     r0, #4
        ADDCC   pc, pc, R0, LSL #2
        B       UnknownHandlerError             ;already defined in ChangeDyn
        B       PreGrow_Screen                  ;the rest are defined here
        B       PostGrow_Screen
        B       PreShrink_Screen
        B       PostShrink_Screen

;The sequence of events which these handlers must do is:
;
;Grow Screen
;
;Pre : Remove cursors
;      Work out which physical page numbers are needed and return a list
;CDA : Move existing pages lower in memory within first copy (ie change logical address
;        associated with physical pages)
;      Locate and free the next physical pages in line (if used a page swap must occur)
;      Assign the new pages logical addresses in the gap between the end of the present
;        logical range and the start of the second physical range
;Post: Adjust screen memory contents & screen start addresses to retain screen display
;
;Shrink Screen
;
;Pre : Remove cursors
;      Adjust screen memory contents & screen start addresses to retain screen display
;CDA : Move pages from screen to free pool (creates a gap in first logical range)
;      Close up the gap in logical addressing
;Post: Restore cursors
;

; ***********************************************************************************
; Handlers for the screen dynamic area
; ***********************************************************************************

;Pregrow entry parameters
; R0 = 0 (reason code)
; R1 -> page block (entries set to -1)
; R2 = number of entries in page block == number of pages area is growing by
; R3 = number of bytes area is growing by (r2 * pagesize)
; R4 = current size (bytes)
; R5 = page size
;
; exit with V clear, all preserved

PreGrow_Screen  Entry   "r0-r2,r4"
        LDR     r0, [WsPtr, #CursorFlags]       ; test if VDU inited yet
        LDRB    lr, [WsPtr, #ExternalFramestore]
        TEQ     r0, #0                          ; if not, CursorFlags will be zero
        BEQ     %FT05
        TEQ     lr, #0
        SWIEQ   XOS_RemoveCursors               ; if VDU inited, then remove cursors

05      ADRL    r0, PageShifts-1
        LDRB    r0, [r0, r5, LSR #12]           ; grab log2Pagesize for shifting
        MOV     r4, r4, LSR r0                  ; change present size into number of pages
                                                ; since page numbers are 0 to n-1 thus n
                                                ; is the first page number we want to insist on
10
        STR     r4, [r1], #12                   ; store physical page number and increment to next
        SUBS    r2, r2, #1                      ; one less to do
        ADDNE   r4, r4, #1                      ; next physical page number
        BNE     %BT10                           ; continue until all pages done
        CLRV                                    ; ok, so I'm paranoid...
        EXIT

; **********************************************************************

;PostGrow entry parameters
;R0 = 1 (reason code)
;R1 -> page block (only physical page numbers are meaningful)
;R2 = number of entries in page block (= number of pages area grew by)
;R3 = number of bytes area grew by
;R4 = new size of area (bytes)
;R5 = page size

PostGrow_Screen Entry   "r0,r5"
        LDR     r0, [WsPtr, #CursorFlags]       ; test if VDU inited (CursorFlags=0 => not)
        TEQ     r0, #0
        BEQ     %FT90                           ; if not inited, do nothing

        PHPSEI  r5                              ; disable IRQs

        MOV     r0, r3                          ; move number of bytes area grew by into r0
        BL      InsertPages                     ; only call InsertPages if VDU inited

        PLP     r5                              ; restore IRQ state
        SWI     XOS_RestoreCursors              ; and restore cursors
90
        CLRV
        EXIT

; ***********************************************************************

;PreShrink Entry parameters
;R0 = 2 (reason code)
;R3 = number of bytes area is shrinking by
;R4 = current size of area (bytes)
;R5 = page size
;R12 = vdu workspace

PreShrink_Screen Entry   "R0-R2,R4-R5"

        ;need to check whether the proposed shrink still leaves enough for
        ;the current amount needed by the vdu drivers, if it doesn't we
        ;reduce R3 to be the most we can spare (in whole pages)

 [ HAL
        LDR     LR, =ZeroPage
        LDR     LR, [LR, #VRAMFlags]            ;is VRAM suitable for general use?
        TST     LR, #2                          ;if not - don't shrink screen memory
        MOVNE   R3, #0
 ]
        SUB     R2, R5, #1                      ;make a page mask

        LDRB    R14, [R12, #ExternalFramestore]
        TEQ     R14, #0                         ;okay to shrink if using external framestore
        BEQ     %FT12
        RSB     R0, R3, #0                      ;R0= -(number of bytes) for RemovePages
        BL      RemovePages
        CLRV
        EXIT

12      LDR     R5, [R12, #ScreenSize]          ;get current minimum size

        SUB     R1, R4, R5                      ;R1 = maximum shrink (current - screensize)
        CMP     R3, R1                          ;if requested shrink > max...
        MOVHI   R3, R1                          ;...then limit it, and...
        BICS    R3, R3, R2                      ;...round down to multiple of page size
        BEQ     %FT10                           ;don't shuffle screen data if resultant
                                                ;shrink is 0 bytes/0 pages
        SWI     XOS_RemoveCursors
        PHPSEI  R5                              ;disable interrupts
        RSB     R0, R3, #0                      ;R0= -(number of bytes) for RemovePages
        BL      RemovePages                     ;entry: R0 = -(number of bytes)
        PLP     R5                              ;restore interrupts
10
        CLRV
        EXIT

; ************************************************************************

;PostShrink Entry parameters
;R0 = 3 (reason code)
;R3 = number of bytes area shrank by
;R4 = new size of area (bytes)
;R5 = page size

PostShrink_Screen Entry
        SWI     XOS_RestoreCursors
        CLRV                                    ;ok, so I'm paranoid...
        EXIT

; ************************************************************************


        END