;--------------------------------------------------------------------
DEBUGMEM MACRO
        IFGE    DEBUG_DETAIL-10
        bsr     fw_DebugMemoryManagement
        ENDC
        ENDM

;--------------------------------------------------------------------
; Initializes the memory stacks for chip- and fast-mem
;
fw_InitMemoryManagement:
        move.l  fw_ChipMemStack(a6),d0
        move.l  fw_ChipMemStackEnd(a6),d1
        move.l  fw_FastMemStack(a6),d2
        move.l  fw_FastMemStackEnd(a6),d3
        PUTMSG  10,<"Chipmem %p - %p, Fastmem %p - %p">,d0,d1,d2,d3

        lea     fw_MemBottomStack(a6),a1
        move.l  d0,cf_ChipMemLevel+mtb_CurrLevelPtr(a1)
        move.l  d0,cf_ChipMemLevel+mtb_MinLevelPtr(a1)
        move.l  d2,cf_FastMemLevel+mtb_CurrLevelPtr(a1)
        move.l  d2,cf_FastMemLevel+mtb_MinLevelPtr(a1)
        ;clr.w   fw_CurrMemBottomLevel(a6)

        lea     fw_MemTopStack(a6),a1
        move.l  d1,cf_ChipMemLevel+mtb_CurrLevelPtr(a1)
        move.l  d1,cf_ChipMemLevel+mtb_MinLevelPtr(a1)
        move.l  d3,cf_FastMemLevel+mtb_CurrLevelPtr(a1)
        move.l  d3,cf_FastMemLevel+mtb_MinLevelPtr(a1)
        ;clr.w   fw_CurrMemTopLevel(a6)

        DEBUGMEM
        rts

;--------------------------------------------------------------------
; Frees all allocated memory
;
; Frees the memory of the current stack and current direction.
; Memory of other allocation direction is unchanged.
;
fw_DropCurrentMemoryAllocations:
        PUTMSG  10,<"%d: DropCurrentMemoryAllocations">,fw_FrameCounter-2(a6)
        IFGE    DEBUG_DETAIL-20
        PUSHM   a0/a1/d0
        ELSE
        PUSHM   a1
        ENDC
        DEBUGMEM
        IF      FW_TOP_BOTTOM_MEM_SECTIONS
        bsr     fw_DetectAllocationDirection
        bne.s   .toptobottom
        ENDC
        PUTMSG  10,<"DOWN: Freeing all">
        lea     fw_MemBottomStack(a6),a1
        adda.w  fw_CurrMemBottomLevel(a6),a1

        IFGE    DEBUG_DETAIL-20
        move.l  cf_ChipMemLevel+mtb_MinLevelPtr(a1),a0
        move.l  cf_ChipMemLevel+mtb_CurrLevelPtr(a1),d0
        sub.l   a0,d0
        bsr     fw_FillWithGarbage

        move.l  cf_FastMemLevel+mtb_MinLevelPtr(a1),a0
        move.l  cf_FastMemLevel+mtb_CurrLevelPtr(a1),d0
        sub.l   a0,d0
        bsr     fw_FillWithGarbage
        ENDC
.cont
        move.l  cf_ChipMemLevel+mtb_MinLevelPtr(a1),cf_ChipMemLevel+mtb_CurrLevelPtr(a1)
        move.l  cf_FastMemLevel+mtb_MinLevelPtr(a1),cf_FastMemLevel+mtb_CurrLevelPtr(a1)

        DEBUGMEM
        POPM
        rts
        IF      FW_TOP_BOTTOM_MEM_SECTIONS
.toptobottom
        PUTMSG  10,<"UP: Freeing all">
        lea     fw_MemTopStack(a6),a1
        adda.w  fw_CurrMemTopLevel(a6),a1

        IFGE    DEBUG_DETAIL-20
        move.l  cf_ChipMemLevel+mtb_CurrLevelPtr(a1),a0
        move.l  cf_ChipMemLevel+mtb_MinLevelPtr(a1),d0
        sub.l   a0,d0
        bsr     fw_FillWithGarbage

        move.l  cf_FastMemLevel+mtb_CurrLevelPtr(a1),a0
        move.l  cf_FastMemLevel+mtb_MinLevelPtr(a1),d0
        sub.l   a0,d0
        bsr     fw_FillWithGarbage
        ENDC
        bra.s   .cont
        ENDC

;--------------------------------------------------------------------
; Pushes the current memory allocation state to the stack
;
; This only pushes the state for the current memory allocation
; direction onto the stack. All allocated memory upto this point
; will be kept safe and will no longer be freed automatically.
;
fw_PushMemoryState:
        PUSHM   a1/d0-d1

        moveq.l #cf_SIZEOF,d0
        IF      FW_TOP_BOTTOM_MEM_SECTIONS
        bsr     fw_DetectAllocationDirection
        bne.s   .toptobottom
        ENDC
        PUTMSG  10,<"UP: ++++++ Pushing mem state ++++++">

        add.w   fw_CurrMemBottomLevel(a6),d0
        move.w  d0,fw_CurrMemBottomLevel(a6)
        lea     fw_MemBottomStack(a6,d0.w),a1
.cont
        move.l  cf_ChipMemLevel+mtb_CurrLevelPtr-cf_SIZEOF(a1),d0
        move.l  cf_FastMemLevel+mtb_CurrLevelPtr-cf_SIZEOF(a1),d1
        move.l  d0,cf_ChipMemLevel+mtb_CurrLevelPtr(a1)
        move.l  d0,cf_ChipMemLevel+mtb_MinLevelPtr(a1)
        move.l  d1,cf_FastMemLevel+mtb_CurrLevelPtr(a1)
        move.l  d1,cf_FastMemLevel+mtb_MinLevelPtr(a1)
        DEBUGMEM
        POPM
        rts
        IF      FW_TOP_BOTTOM_MEM_SECTIONS
.toptobottom
        PUTMSG  10,<"DOWN: ++++++ Pushing mem state ++++++">
        add.w   fw_CurrMemTopLevel(a6),d0
        move.w  d0,fw_CurrMemTopLevel(a6)
        lea     fw_MemTopStack(a6,d0.w),a1
        bra.s   .cont
        ENDC

;--------------------------------------------------------------------
; Restore the last memory state from the memory stack for the current
; allocation direction. All memory from the previous allocation is
; freed.
;
; With debug enabled, freed memory is also overwritten with garbage.
;
; Out: All registers unchanged.
;
fw_PopMemoryState:
        PUSHM   a0/d0
        DEBUGMEM

        IF      FW_TOP_BOTTOM_MEM_SECTIONS
        bsr     fw_DetectAllocationDirection
        bne     .toptobottom
        ENDC
        IFGE    DEBUG_DETAIL-10
        PUTMSG  10,<"UP: ------ Popping mem state ------">
        bsr     fw_DropCurrentMemoryAllocations
        sub.w   #cf_SIZEOF,fw_CurrMemBottomLevel(a6)
        bmi.s   .errorbottom
        ELSE
        sub.w   #cf_SIZEOF,fw_CurrMemBottomLevel(a6)
        ENDC
.cont
        DEBUGMEM
        POPM
        rts
        IFGE    DEBUG_DETAIL-10
.errorbottom
        PUTMSG  10,<"!!! Could not pop bottom memory stack (TOP: %d vs %d)">,fw_CurrMemBottomLevel-2(a6),fw_CurrMemTopLevel-2(a6)
        move.w  #ERROR_MEMORYWRONGPOP,d0
        bra     fw_Error
.errortop
        PUTMSG  10,<"!!! Could not pop top memory stack (TOP: %d vs %d)">,fw_CurrMemBottomLevel-2(a6),fw_CurrMemTopLevel-2(a6)
        move.w  #ERROR_MEMORYWRONGPOP,d0
        bra     fw_Error
        ENDC
        IF      FW_TOP_BOTTOM_MEM_SECTIONS
.toptobottom
        IFGE    DEBUG_DETAIL-10
        PUTMSG  10,<"DOWN: ------ Popping mem state ------">
        bsr     fw_DropCurrentMemoryAllocations
        sub.w   #cf_SIZEOF,fw_CurrMemTopLevel(a6)
        bmi     .errortop
        ELSE
        sub.w   #cf_SIZEOF,fw_CurrMemTopLevel(a6)
        ENDC
        bra     .cont
        ENDC

;--------------------------------------------------------------------
; Allocates the given amount of fastmem in the current direction of
; memory allocation (bottom->top or top->bottom).
;
; If theres not enough fast-mem, it falls back and returns chip-mem
; instead.
;
; Contents of memory are not cleared!
;
; In : d0: Size in bytes
; Out: a0: Start of memory allocation
;      d0: Rounded size of allocation
;      d1/a1: Trashed.
;
fw_AllocFast:
        PUTMSG  10,<"%d: AllocFast(%ld)">,fw_FrameCounter-2(a6),d0
        addq.l  #3,d0
        and.w   #-4,d0

        lea     fw_MemBottomStack(a6),a0
        adda.w  fw_CurrMemBottomLevel(a6),a0
        lea     fw_MemTopStack(a6),a1
        adda.w  fw_CurrMemTopLevel(a6),a1

        move.l  cf_FastMemLevel+mtb_CurrLevelPtr(a1),d1
        sub.l   cf_FastMemLevel+mtb_CurrLevelPtr(a0),d1
        cmp.l   d0,d1
        blo.s   fw_AllocChip

        moveq.l #cf_FastMemLevel,d1
        bsr     fw_DoAllocation
        PUTMSG  30,<"Fast allocated at %p">,a0
        IFGE    DEBUG_DETAIL-12
        bsr.s   fw_FillWithGarbage
        ENDC
        DEBUGMEM
        rts

;--------------------------------------------------------------------
; Allocates the given amount of chipmem in the current direction of
; memory allocation (bottom->top or top->bottom).
;
; Contents of memory are not cleared!
;
; In : d0: Size in bytes
; Out: a0: Start of memory allocation
;      d0: Rounded size of allocation
;      d1/a1: Trashed.
;
fw_AllocChip:
        PUTMSG  10,<"%d: AllocChip(%ld)">,fw_FrameCounter-2(a6),d0
        addq.l  #7,d0
        and.w   #-8,d0

        lea     fw_MemBottomStack(a6),a0
        adda.w  fw_CurrMemBottomLevel(a6),a0
        lea     fw_MemTopStack(a6),a1
        adda.w  fw_CurrMemTopLevel(a6),a1

        move.l  cf_ChipMemLevel+mtb_CurrLevelPtr(a1),d1
        sub.l   cf_ChipMemLevel+mtb_CurrLevelPtr(a0),d1
        cmp.l   d0,d1
        blo.s   .error

        moveq.l #cf_ChipMemLevel,d1
        bsr     fw_DoAllocation

        PUTMSG  30,<"Chip allocated at %p">,a0
        IFGE    DEBUG_DETAIL-12
        bsr     fw_FillWithGarbage
        ENDC
        DEBUGMEM
        rts
.error
        PUTMSG  10,<"Out of memory: %ld smaller than %ld">,d1,d0
        DEBUGMEM
        move.w  #ERROR_OUTOFMEMORY,d0
        bra     fw_Error

;--------------------------------------------------------------------
; Allocates the given amount of chipmem within a 64 KB page in the
; current direction of memory allocation.
;
; Memory area returned is guaranteed not to cross a 64 KB page
; boundary for the given size. Depending on the current memory
; situation and size requirements, may waste up to 64 KB of memory
; in the worst case. If you need multiple allocations these
; requirements, make sure to cluster them in a good way.
;
; Contents of memory are not cleared!
;
; In : d0: Size in bytes (less or equal to 64 KB!)
; Out: a0: Start of memory allocation
;      d0: Rounded size of allocation
;      d1/a1: Trashed.
;
        IF      FW_64KB_PAGE_MEMORY_SUPPORT
fw_AllocChip64KB:
        PUTMSG  10,<"%d: AllocChip64KB(%ld)">,fw_FrameCounter-2(a6),d0
        addq.l  #7,d0
        and.w   #-8,d0

        lea     fw_MemBottomStack(a6),a0
        adda.w  fw_CurrMemBottomLevel(a6),a0
        lea     fw_MemTopStack(a6),a1
        adda.w  fw_CurrMemTopLevel(a6),a1

.retry
        move.l  cf_ChipMemLevel+mtb_CurrLevelPtr(a1),d1
        sub.l   cf_ChipMemLevel+mtb_CurrLevelPtr(a0),d1
        cmp.l   d0,d1
        blo     .error

        IF      FW_TOP_BOTTOM_MEM_SECTIONS
        bsr     fw_DetectAllocationDirection
        bne.s   .toptobottom
        ENDC

        move.l  cf_ChipMemLevel+mtb_CurrLevelPtr(a0),d1
        add.l   d0,d1
        subq.l  #1,d1
        swap    d1
        cmp.w   cf_ChipMemLevel+mtb_CurrLevelPtr(a0),d1
        beq     .doallocation

        addq.w  #1,cf_ChipMemLevel+mtb_CurrLevelPtr(a0)
        IFGE    DEBUG_DETAIL-10
        moveq.l #0,d1
        move.w  cf_ChipMemLevel+mtb_CurrLevelPtr+2(a0),d1
        neg.w   d1
        PUTMSG  10,<"Skipping %ld bytes of memory DOWN (sorry)">,d1
        ENDC
        clr.w   cf_ChipMemLevel+mtb_CurrLevelPtr+2(a0)
        bra.s   .retry

        IF      FW_TOP_BOTTOM_MEM_SECTIONS
.toptobottom
        moveq.l #0,d1
        move.w  cf_ChipMemLevel+mtb_CurrLevelPtr+2(a1),d1
        beq.s   .doallocation
        cmp.l   d0,d1
        bge.s   .doallocation

        PUTMSG  10,<"Skipping %d bytes of memory UP (sorry)">,cf_ChipMemLevel+mtb_CurrLevelPtr(a1)
        clr.w   cf_ChipMemLevel+mtb_CurrLevelPtr+2(a1)
        bra     .retry
        ENDC

.doallocation
        moveq.l #cf_ChipMemLevel,d1
        bsr     fw_DoAllocation

        PUTMSG  30,<"Chip within 64 KB page allocated at %p">,a0
        IFGE    DEBUG_DETAIL-20
        bsr     fw_FillWithGarbage
        ENDC
        DEBUGMEM
        rts
.error
        PUTMSG  10,<"Out of memory: %ld smaller than %ld">,d1,d0
        DEBUGMEM
        move.w  #ERROR_OUTOFMEMORY,d0
        bra     fw_Error
        ENDC

;--------------------------------------------------------------------
; Changes the direction of memory allocation (bottom to top vs top
; to bottom). Also works from subtasks (be a bit careful there).
;
; Out: All registers unchanged.
;
        IF      FW_TOP_BOTTOM_MEM_SECTIONS
fw_FlipAllocationDirection:
        IF      FW_MULTITASKING_SUPPORT
        tst.l   fw_BackgroundTask(a6)
        beq.s   .flipmain
        PUSHM   a0
        move.l  fw_BackgroundTask(a6),a0
        eor.w   #1,ft_MemDirection(a0)
        PUTMSG  10,<"Flipping allocation direction of task %p (%s) to %d">,a0,LN_NAME(a0),ft_MemDirection-2(a0)
        POPM
        rts
        ENDC
.flipmain
        eor.w   #1,fw_MainMemDirection(a6)
        PUTMSG  10,<"Flipping main allocation direction to %d">,fw_MainMemDirection-2(a6)
        rts

;--------------------------------------------------------------------

fw_DetectAllocationDirection:
        IF      FW_MULTITASKING_SUPPORT
        tst.l   fw_BackgroundTask(a6)
        beq.s   .flipmain
        PUSHM   a0/d0 ; avoid optimizing the movem to move so that the CCs remain intact
        move.l  fw_BackgroundTask(a6),a0
        tst.w   ft_MemDirection(a0)
        POPM
        rts
        ENDC
.flipmain
        tst.w   fw_MainMemDirection(a6)
        rts
        ENDC

;--------------------------------------------------------------------

fw_DoAllocation:
        PUSHM   a1/d0/d1
        IF      FW_TOP_BOTTOM_MEM_SECTIONS
        bsr     fw_DetectAllocationDirection
        bne.s   .toptobottom
        ENDC
        ; allocate from bottom
        lea     fw_MemBottomStack(a6),a1
        adda.w  fw_CurrMemBottomLevel(a6),a1
        move.l  mtb_CurrLevelPtr(a1,d1.w),a0
        PUTMSG  10,<"UP: Allocating %ld bytes from bottom at %p">,d0,a0
        add.l   a0,d0
        move.l  d0,mtb_CurrLevelPtr(a1,d1.w)
.cont
        POPM
        rts
        IF      FW_TOP_BOTTOM_MEM_SECTIONS
.toptobottom
        ; allocate from top
        lea     fw_MemTopStack(a6),a1
        adda.w  fw_CurrMemTopLevel(a6),a1
        move.l  mtb_CurrLevelPtr(a1,d1.w),a0
        sub.l   d0,a0
        PUTMSG  10,<"DOWN: Allocating %ld bytes from top at %p">,d0,a0
        move.l  a0,mtb_CurrLevelPtr(a1,d1.w)
        bra.s   .cont
        ENDC

;--------------------------------------------------------------------

        IFGE    DEBUG_DETAIL-12
fw_FillWithGarbage:
        PUTMSG  10,<"Filling with garbage %p (%ld)">,a0,d0
        PUSHM   a0/d0/d1
        lsr.l   #2,d0
        beq.s   .skipfill
        subq.w  #1,d0
        move.l  #$DEADBE00,d1
.fillloop
        move.l  d1,(a0)+
        addq.l  #1,d1
        dbra    d0,.fillloop
        swap    d0
        subq.w  #1,d0
        bcs.s   .skipfill
        swap    d0
        bra.s   .fillloop
.skipfill
        POPM
        rts
        ENDC

;--------------------------------------------------------------------

        IFGE    DEBUG_DETAIL-10
fw_DebugMemoryManagement:
        PUSHM   d0-d7/a0/a1
        lea     fw_MemBottomStack(a6),a0
        adda.w  fw_CurrMemBottomLevel(a6),a0
        lea     fw_MemTopStack(a6),a1
        adda.w  fw_CurrMemTopLevel(a6),a1

        move.l  cf_ChipMemLevel+mtb_CurrLevelPtr(a1),d0 ; current free chip: chip top - bottom
        sub.l   cf_ChipMemLevel+mtb_CurrLevelPtr(a0),d0
        lsr.l   #8,d0
        lsr.w   #2,d0

        move.l  fw_MemTopStack+cf_ChipMemLevel+mtb_MinLevelPtr(a6),d1  ; max free chip within this frame
        sub.l   cf_ChipMemLevel+mtb_MinLevelPtr(a0),d1
        lsr.l   #8,d1
        lsr.w   #2,d1

        move.l  cf_ChipMemLevel+mtb_CurrLevelPtr(a0),d2 ; bottom chip allocated in stack
        sub.l   cf_ChipMemLevel+mtb_MinLevelPtr(a0),d2
        lsr.l   #8,d2
        lsr.w   #2,d2

        move.l  cf_ChipMemLevel+mtb_MinLevelPtr(a1),d3 ; top chip allocated in stack
        sub.l   cf_ChipMemLevel+mtb_CurrLevelPtr(a1),d3
        lsr.l   #8,d3
        lsr.w   #2,d3

        move.l  cf_FastMemLevel+mtb_CurrLevelPtr(a1),d4 ; current free fast: fast top - bottom
        sub.l   cf_FastMemLevel+mtb_CurrLevelPtr(a0),d4
        lsr.l   #8,d4
        lsr.w   #2,d4

        move.l  cf_FastMemLevel+mtb_MinLevelPtr(a1),d5  ; max free fast within this frame
        sub.l   fw_MemBottomStack+cf_FastMemLevel+mtb_MinLevelPtr(a6),d5
        lsr.l   #8,d5
        lsr.w   #2,d5

        move.l  cf_FastMemLevel+mtb_CurrLevelPtr(a0),d6 ; bottom fast allocated in stack
        sub.l   cf_FastMemLevel+mtb_MinLevelPtr(a0),d6
        lsr.l   #8,d6
        lsr.w   #2,d6

        move.l  cf_FastMemLevel+mtb_MinLevelPtr(a1),d7 ; top fast allocated in stack
        sub.l   cf_FastMemLevel+mtb_CurrLevelPtr(a1),d7
        lsr.l   #8,d7
        lsr.w   #2,d7

        PUTMSG  10,<"Mem Free: Chip: %ld of %ld KB (%ld/%ld KB) | Fast: %ld of %ld KB (%ld/%ld KB)">,d0,d1,d2,d3,d4,d5,d6,d7

        move.l  cf_ChipMemLevel+mtb_CurrLevelPtr(a0),d0 ; bottom chip allocated total
        sub.l   fw_MemBottomStack+cf_ChipMemLevel+mtb_MinLevelPtr(a6),d0
        add.l   fw_MemTopStack+cf_ChipMemLevel+mtb_MinLevelPtr(a6),d0 ; top chip allocated total
        sub.l   cf_ChipMemLevel+mtb_CurrLevelPtr(a1),d0
        lsr.l   #8,d0
        lsr.w   #2,d0
        cmp.l   fw_MaxChipUsed(a6),d0
        blt.s   .lesschip
        move.l  d0,fw_MaxChipUsed(a6)
.lesschip

        move.l  fw_MemTopStack+cf_ChipMemLevel+mtb_MinLevelPtr(a6),d1  ; max free chip total
        sub.l   fw_MemBottomStack+cf_ChipMemLevel+mtb_MinLevelPtr(a6),d1
        lsr.l   #8,d1
        lsr.w   #2,d1

        move.l  cf_FastMemLevel+mtb_CurrLevelPtr(a0),d2 ; bottom fast allocated total
        sub.l   fw_MemBottomStack+cf_FastMemLevel+mtb_MinLevelPtr(a6),d2
        add.l   fw_MemTopStack+cf_FastMemLevel+mtb_MinLevelPtr(a6),d2 ; top fast allocated total
        sub.l   cf_FastMemLevel+mtb_CurrLevelPtr(a1),d2
        lsr.l   #8,d2
        lsr.w   #2,d2
        cmp.l   fw_MaxFastUsed(a6),d2
        blt.s   .lessfast
        move.l  d2,fw_MaxFastUsed(a6)
.lessfast

        move.l  fw_MemTopStack+cf_FastMemLevel+mtb_MinLevelPtr(a6),d3  ; max free fast total
        sub.l   fw_MemBottomStack+cf_FastMemLevel+mtb_MinLevelPtr(a6),d3
        lsr.l   #8,d3
        lsr.w   #2,d3

        PUTMSG  10,<"Mem Used: Chip: %ld of %ld KB (max. %ld KB) | Fast: %ld of %ld KB (max. %ld KB)">,d0,d1,fw_MaxChipUsed(a6),d2,d3,fw_MaxFastUsed(a6)

        PUTMSG  20,<"Chip Bottom: %p, Chip Top %p, Fast Bottom: %p, Fast Top: %p">,cf_ChipMemLevel+mtb_CurrLevelPtr(a0),cf_ChipMemLevel+mtb_CurrLevelPtr(a1),cf_FastMemLevel+mtb_CurrLevelPtr(a0),cf_FastMemLevel+mtb_CurrLevelPtr(a1)
        POPM
        rts
        ENDC
