heap.inc
mxlgv_KolibriDev/src/kernel/core/heap.inc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                              ;;
;; Copyright (C) KolibriOS team 2004-2020. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License    ;;
;;                                                              ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

$Revision: 7968 $


struct  MEM_BLOCK
        list            LHEAD
        next_block      dd ? ;+8
        prev_block      dd ? ;+4
        base            dd ? ;+16
        size            dd ? ;+20
        flags           dd ? ;+24
        handle          dd ? ;+28
ends

MEM_BLOCK_RESERVED  = 0x02      ; Will be allocated on first access (lazy allocation)
MEM_BLOCK_FREE      = 0x04
MEM_BLOCK_USED      = 0x08
MEM_BLOCK_DONT_FREE = 0x10

macro calc_index op
{          shr op, 12
        dec     op
        cmp     op, 63
        jna     @f
        mov     op, 63
@@:
}

align 4
md:
.add_to_used:
        mov     eax, [esi + MEM_BLOCK.base]
        mov     ebx, [esi + MEM_BLOCK.base]
        shr     ebx, 6
        add     eax, ebx
        shr     ebx, 6
        add     eax, ebx
        shr     eax, 12
        and     eax, 63
        inc     [mem_hash_cnt + eax*4]

        lea     ecx, [mem_used_list + eax*8]
        list_add esi, ecx
        mov     [esi + MEM_BLOCK.flags], MEM_BLOCK_USED
        mov     eax, [esi + MEM_BLOCK.size]
        sub     [heap_free], eax
        ret
align 4
.find_used:
        mov     ecx, eax
        mov     ebx, eax
        shr     ebx, 6
        add     ecx, ebx
        shr     ebx, 6
        add     ecx, ebx
        shr     ecx, 12
        and     ecx, 63

        lea     ebx, [mem_used_list + ecx*8]
        mov     esi, ebx
.next:
        mov     esi, [esi + MEM_BLOCK.list.next]
        cmp     esi, ebx
        je      .fail

        cmp     eax, [esi + MEM_BLOCK.base]
        jne     .next

        ret
.fail:
        xor     esi, esi
        ret

align 4
.del_from_used:
        call    .find_used
        test    esi, esi
        jz      .done

        cmp     [esi + MEM_BLOCK.flags], MEM_BLOCK_USED
        jne     .fatal

        dec     [mem_hash_cnt + ecx*4]
        list_del esi
.done:
        ret
.fatal:                            ;FIXME panic here
        xor     esi, esi
        ret

;Initial heap state
;
; + heap_size               terminator        MEM_BLOCK_USED
; + 4096*MEM_BLOCK.sizeof   free space        MEM_BLOCK_FREE
;HEAP_BASE                heap_descriptors  MEM_BLOCK_USED
;

align 4
proc init_kernel_heap

        mov     ecx, 64
        mov     edi, mem_block_list
  @@:
        mov     eax, edi
        stosd
        stosd
        loop    @B

        mov     ecx, 64
        mov     edi, mem_used_list
  @@:
        mov     eax, edi
        stosd
        stosd
        loop    @B

        stdcall alloc_pages, dword 32

        or      eax, PG_SWR
        mov     ebx, HEAP_BASE
        mov     ecx, 32
        call    commit_pages

        mov     edi, HEAP_BASE                     ;descriptors
        mov     ebx, HEAP_BASE + sizeof.MEM_BLOCK      ;free space
        mov     ecx, HEAP_BASE + sizeof.MEM_BLOCK*2    ;terminator

        xor     eax, eax
        mov     [edi + MEM_BLOCK.next_block], ebx
        mov     [edi + MEM_BLOCK.prev_block], eax
        mov     [edi + MEM_BLOCK.list.next], eax
        mov     [edi + MEM_BLOCK.list.prev], eax
        mov     [edi + MEM_BLOCK.base], HEAP_BASE
        mov     [edi + MEM_BLOCK.size], 4096*sizeof.MEM_BLOCK
        mov     [edi + MEM_BLOCK.flags], MEM_BLOCK_USED

        mov     [ecx + MEM_BLOCK.next_block], eax
        mov     [ecx + MEM_BLOCK.prev_block], ebx
        mov     [ecx + MEM_BLOCK.list.next], eax
        mov     [ecx + MEM_BLOCK.list.prev], eax
        mov     [ecx + MEM_BLOCK.base], eax
        mov     [ecx + MEM_BLOCK.size], eax
        mov     [ecx + MEM_BLOCK.flags], MEM_BLOCK_USED

        mov     [ebx + MEM_BLOCK.next_block], ecx
        mov     [ebx + MEM_BLOCK.prev_block], edi
        mov     [ebx + MEM_BLOCK.base], HEAP_BASE + 4096*sizeof.MEM_BLOCK

        mov     ecx, [pg_data.kernel_pages]
        shl     ecx, 12
        sub     ecx, HEAP_BASE-OS_BASE + 4096*sizeof.MEM_BLOCK
        mov     [heap_size], ecx
        mov     [heap_free], ecx
        mov     [ebx + MEM_BLOCK.size], ecx
        mov     [ebx + MEM_BLOCK.flags], MEM_BLOCK_FREE

        mov     [mem_block_mask], eax
        mov     [mem_block_mask + 4], 0x80000000

        mov     ecx, mem_block_list + 63*8
        list_add ebx, ecx

        mov     ecx, 4096-3-1
        mov     eax, HEAP_BASE + sizeof.MEM_BLOCK*4

        mov     [next_memblock], HEAP_BASE + sizeof.MEM_BLOCK *3
 @@:
        mov     [eax-sizeof.MEM_BLOCK], eax
        add     eax, sizeof.MEM_BLOCK
        loop    @B

        mov     dword[eax-sizeof.MEM_BLOCK], 0

        mov     ecx, heap_mutex
        call    mutex_init
        mov     [heap_blocks], 4094
        mov     [free_blocks], 4093
        ret
endp

; param
;  eax= required size
;
; retval
;  edi= memory block descriptor
;  ebx= descriptor index

align 4
get_small_block:
        mov     ecx, eax
        shr     ecx, 12
        dec     ecx
        cmp     ecx, 63
        jle     .get_index
        mov     ecx, 63
  .get_index:
        lea     esi, [mem_block_mask]
        xor     ebx, ebx
        or      edx, -1

        cmp     ecx, 32
        jb      .bit_test

        sub     ecx, 32
        add     ebx, 32
        add     esi, 4
  .bit_test:
        shl     edx, cl
        and     edx, [esi]
  .find:
        bsf     edi, edx
        jz      .high_mask
        add     ebx, edi
        lea     ecx, [mem_block_list + ebx*8]
        mov     edi, ecx
  .next:
        mov     edi, [edi + MEM_BLOCK.list.next]
        cmp     edi, ecx
        je      .err
        cmp     eax, [edi + MEM_BLOCK.size]
        ja      .next
        ret
  .err:
        xor     edi, edi
        ret

  .high_mask:
        add     esi, 4
        cmp     esi, mem_block_mask + 8
        jae     .err
        add     ebx, 32
        mov     edx, [esi]
        jmp     .find


align 4
free_mem_block:

        mov     ebx, [next_memblock]
        mov     [eax], ebx
        mov     [next_memblock], eax

        xor     ebx, ebx
        mov     dword[eax + 4], ebx
        mov     dword[eax + 8], ebx
        mov     dword[eax + 12], ebx
        mov     dword[eax + 16], ebx
;           mov dword[eax + 20], 0     ;don't clear block size
        mov     dword[eax + 24], ebx
        mov     dword[eax + 28], ebx

        inc     [free_blocks]

        ret

align 4
proc alloc_kernel_space stdcall, size:dword
           local block_ind:DWORD

        push    ebx
        push    esi
        push    edi

        mov     eax, [size]
        add     eax, 4095
        and     eax, not 4095
        mov     [size], eax

        cmp     eax, [heap_free]
        ja      .error

        spin_lock_irqsave heap_mutex

        mov     eax, [size]

        call    get_small_block ; eax
        test    edi, edi
        jz      .error_unlock

        cmp     [edi + MEM_BLOCK.flags], MEM_BLOCK_FREE
        jne     .error_unlock

        mov     [block_ind], ebx  ;index of allocated block

        mov     eax, [edi + MEM_BLOCK.size]
        cmp     eax, [size]
        je      .m_eq_size

        mov     esi, [next_memblock]    ;new memory block
        test    esi, esi
        jz      .error_unlock

        dec     [free_blocks]
        mov     eax, [esi]
        mov     [next_memblock], eax

        mov     [esi + MEM_BLOCK.next_block], edi
        mov     eax, [edi + MEM_BLOCK.prev_block]
        mov     [esi + MEM_BLOCK.prev_block], eax
        mov     [edi + MEM_BLOCK.prev_block], esi
        mov     [esi + MEM_BLOCK.list.next], 0
        mov     [esi + MEM_BLOCK.list.prev], 0
        mov     [eax + MEM_BLOCK.next_block], esi

        mov     ebx, [edi + MEM_BLOCK.base]
        mov     [esi + MEM_BLOCK.base], ebx
        mov     edx, [size]
        mov     [esi + MEM_BLOCK.size], edx
        add     [edi + MEM_BLOCK.base], edx
        sub     [edi + MEM_BLOCK.size], edx

        mov     eax, [edi + MEM_BLOCK.size]
        calc_index eax
        cmp     eax, [block_ind]
        je      .add_used

        list_del edi

        mov     ecx, [block_ind]
        lea     edx, [mem_block_list + ecx*8]
        cmp     edx, [edx]
        jnz     @f
        btr     [mem_block_mask], ecx
@@:
        bts     [mem_block_mask], eax
        lea     edx, [mem_block_list + eax*8]  ;edx= list head
        list_add edi, edx
.add_used:

        call    md.add_to_used

        spin_unlock_irqrestore heap_mutex
        mov     eax, [esi + MEM_BLOCK.base]
        pop     edi
        pop     esi
        pop     ebx
        ret

.m_eq_size:
        list_del edi
        lea     edx, [mem_block_list + ebx*8]
        cmp     edx, [edx]
        jnz     @f
        btr     [mem_block_mask], ebx
@@:
        mov     esi, edi
        jmp     .add_used

.error_unlock:
        spin_unlock_irqrestore heap_mutex
.error:
        xor     eax, eax
        pop     edi
        pop     esi
        pop     ebx
        ret
endp

align 4
proc free_kernel_space stdcall uses ebx ecx edx esi edi, base:dword

        spin_lock_irqsave heap_mutex

        mov     eax, [base]

        call    md.del_from_used
        test    esi, esi
        jz      .fail

        mov     eax, [esi + MEM_BLOCK.size]
        add     [heap_free], eax

        mov     edi, [esi + MEM_BLOCK.next_block]
        cmp     [edi + MEM_BLOCK.flags], MEM_BLOCK_FREE
        jne     .prev

        list_del edi

        mov     edx, [edi + MEM_BLOCK.next_block]
        mov     [esi + MEM_BLOCK.next_block], edx
        mov     [edx + MEM_BLOCK.prev_block], esi
        mov     ecx, [edi + MEM_BLOCK.size]
        add     [esi + MEM_BLOCK.size], ecx

        calc_index ecx

        lea     edx, [mem_block_list + ecx*8]
        cmp     edx, [edx]
        jne     @F
        btr     [mem_block_mask], ecx
@@:
        mov     eax, edi
        call    free_mem_block
.prev:
        mov     edi, [esi + MEM_BLOCK.prev_block]
        cmp     [edi + MEM_BLOCK.flags], MEM_BLOCK_FREE
        jne     .insert

        mov     edx, [esi + MEM_BLOCK.next_block]
        mov     [edi + MEM_BLOCK.next_block], edx
        mov     [edx + MEM_BLOCK.prev_block], edi

        mov     eax, esi
        call    free_mem_block

        mov     ecx, [edi + MEM_BLOCK.size]
        mov     eax, [esi + MEM_BLOCK.size]
        add     eax, ecx
        mov     [edi + MEM_BLOCK.size], eax

        calc_index eax                     ;new index
        calc_index ecx                     ;old index
        cmp     eax, ecx
        je      .m_eq

        push    ecx
        list_del edi
        pop     ecx

        lea     edx, [mem_block_list + ecx*8]
        cmp     edx, [edx]
        jne     .add_block
        btr     [mem_block_mask], ecx

.add_block:
        bts     [mem_block_mask], eax
        lea     edx, [mem_block_list + eax*8]
        list_add edi, edx
.m_eq:
        spin_unlock_irqrestore heap_mutex
        xor     eax, eax
        not     eax
        ret
.insert:
        mov     [esi + MEM_BLOCK.flags], MEM_BLOCK_FREE
        mov     eax, [esi + MEM_BLOCK.size]
        calc_index eax
        mov     edi, esi
        jmp     .add_block

.fail:
        spin_unlock_irqrestore heap_mutex
        xor     eax, eax
        ret
endp

align 4
proc kernel_alloc stdcall, size:dword
        locals
          lin_addr    dd ?
          pages_count dd ?
        endl

        push    ebx
        push    edi

        mov     eax, [size]
        add     eax, 4095
        and     eax, not 4095;
        mov     [size], eax
        and     eax, eax
        jz      .err
        mov     ebx, eax
        shr     ebx, 12
        mov     [pages_count], ebx

        stdcall alloc_kernel_space, eax
        mov     [lin_addr], eax
        mov     ebx, [pages_count]
        test    eax, eax
        jz      .err

        mov     edx, eax

        shr     ebx, 3
        jz      .tail

        shl     ebx, 3
        stdcall alloc_pages, ebx
        test    eax, eax
        jz      .err

        mov     ecx, ebx
        or      eax, PG_GLOBAL + PG_SWR
        mov     ebx, [lin_addr]
        call    commit_pages

        mov     edx, ebx                    ; this dirty hack
.tail:
        mov     ebx, [pages_count]
        and     ebx, 7
        jz      .end
@@:
        call    alloc_page
        test    eax, eax
        jz      .err

        stdcall map_page, edx, eax, dword (PG_GLOBAL + PG_SWR)
        add     edx, 0x1000
        dec     ebx
        jnz     @B
.end:
        mov     eax, [lin_addr]
        pop     edi
        pop     ebx
        ret
.err:
        xor     eax, eax
        pop     edi
        pop     ebx
        ret
endp

align 4
proc kernel_free stdcall, base:dword

        push    ebx esi

        spin_lock_irqsave heap_mutex

        mov     eax, [base]
        call    md.find_used

        cmp     [esi + MEM_BLOCK.flags], MEM_BLOCK_USED
        jne     .fail

        spin_unlock_irqrestore heap_mutex

        mov     eax, [esi + MEM_BLOCK.base]
        mov     ecx, [esi + MEM_BLOCK.size]
        shr     ecx, 12
        call    release_pages   ;eax, ecx
        stdcall free_kernel_space, [base]
        pop     esi ebx
        ret
.fail:
        spin_unlock_irqrestore heap_mutex
        xor     eax, eax
        pop     esi ebx
        ret
endp

;;;;;;;;;;;;;;      USER HEAP     ;;;;;;;;;;;;;;;;;

HEAP_TOP  = 0x80000000

align 4
proc init_heap

        mov     ebx, [current_process]
        mov     eax, [ebx + PROC.heap_top]
        test    eax, eax
        jz      @F
        sub     eax, [ebx + PROC.heap_base]
        sub     eax, PAGE_SIZE
        ret
@@:
        lea     ecx, [ebx + PROC.heap_lock]
        call    mutex_init

        mov     esi, [ebx + PROC.mem_used]
        add     esi, 4095
        and     esi, not 4095
        mov     [ebx + PROC.mem_used], esi
        mov     eax, HEAP_TOP
        mov     [ebx + PROC.heap_base], esi
        mov     [ebx + PROC.heap_top], eax

        sub     eax, esi
        shr     esi, 10
        mov     ecx, eax
        sub     eax, PAGE_SIZE
        or      ecx, MEM_BLOCK_FREE
        mov     [page_tabs + esi], ecx
        ret
endp

align 4
proc user_alloc stdcall, alloc_size:dword

        push    ebx esi edi

        mov     ebx, [current_process]
        lea     ecx, [ebx + PROC.heap_lock]
        call    mutex_lock

        mov     ecx, [alloc_size]
        add     ecx, (4095 + PAGE_SIZE)
        and     ecx, not 4095
        mov     esi, [ebx + PROC.heap_base]
        mov     edi, [ebx + PROC.heap_top]
  .scan:
        cmp     esi, edi
        jae     .m_exit

        mov     ebx, esi
        shr     ebx, 12
        mov     eax, [page_tabs + ebx*4]
        test    al, MEM_BLOCK_FREE
        jz      .test_used
        and     eax, 0xFFFFF000
        cmp     eax, ecx   ;alloc_size
        jb      .m_next
        jz      @f

        lea     edx, [esi + ecx]
        sub     eax, ecx
        or      al, MEM_BLOCK_FREE
        shr     edx, 12
        mov     [page_tabs + edx*4], eax
  @@:
        or      ecx, MEM_BLOCK_USED
        mov     [page_tabs + ebx*4], ecx
        shr     ecx, 12
        inc     ebx
        dec     ecx
        jz      .no
  @@:
        mov     dword [page_tabs + ebx*4], MEM_BLOCK_RESERVED
        inc     ebx
        dec     ecx
        jnz     @B
  .no:

        mov     edx, [current_process]
        mov     ebx, [alloc_size]
        add     ebx, 0xFFF
        and     ebx, not 0xFFF
        add     [edx + PROC.mem_used], ebx

        lea     ecx, [edx + PROC.heap_lock]
        call    mutex_unlock

        lea     eax, [esi + 4096]

        pop     edi
        pop     esi
        pop     ebx
        ret
.test_used:
        test    al, MEM_BLOCK_USED
        jz      .m_exit

        and     eax, 0xFFFFF000 ; not PAGESIZE
.m_next:
        add     esi, eax
        jmp     .scan
.m_exit:
        mov     ecx, [current_process]
        lea     ecx, [ecx + PROC.heap_lock]
        call    mutex_unlock

        xor     eax, eax
        pop     edi
        pop     esi
        pop     ebx
        ret
endp

align 4
proc user_alloc_at stdcall, address:dword, alloc_size:dword

        push    ebx
        push    esi
        push    edi

        mov     ebx, [current_process]
        lea     ecx, [ebx + PROC.heap_lock]
        call    mutex_lock

        mov     edx, [address]
        and     edx, not 0xFFF
        mov     [address], edx
        sub     edx, 0x1000
        jb      .error
        mov     esi, [ebx + PROC.heap_base]
        mov     edi, [ebx + PROC.heap_top]
        cmp     edx, esi
        jb      .error
.scan:
        cmp     esi, edi
        jae     .error
        mov     ebx, esi
        shr     ebx, 12
        mov     eax, [page_tabs + ebx*4]
        mov     ecx, eax
        and     ecx, 0xFFFFF000
        add     ecx, esi
        cmp     edx, ecx
        jb      .found
        mov     esi, ecx
        jmp     .scan
.error:
        mov     ecx, [current_process]
        lea     ecx, [ecx + PROC.heap_lock]
        call    mutex_unlock

        xor     eax, eax
        pop     edi
        pop     esi
        pop     ebx
        ret
.found:
        test    al, MEM_BLOCK_FREE
        jz      .error
        mov     eax, ecx
        sub     eax, edx
        sub     eax, 0x1000
        cmp     eax, [alloc_size]
        jb      .error

; Here we have 1 big free block which includes requested area.
; In general, 3 other blocks must be created instead:
; free at [esi, edx);
; busy at [edx, edx + 0x1000 + ALIGN_UP(alloc_size,0x1000));
; free at [edx + 0x1000 + ALIGN_UP(alloc_size,0x1000), ecx)
; First or third block (or both) may be absent.
        mov     eax, edx
        sub     eax, esi
        jz      .nofirst
        or      al, MEM_BLOCK_FREE
        mov     [page_tabs + ebx*4], eax
  .nofirst:
        mov     eax, [alloc_size]
        add     eax, 0x1FFF
        and     eax, not 0xFFF
        mov     ebx, edx
        add     edx, eax
        shr     ebx, 12
        or      al, MEM_BLOCK_USED
        mov     [page_tabs + ebx*4], eax
        shr     eax, 12
        dec     eax
        jz      .second_nofill
        inc     ebx
  .fill:
        mov     dword [page_tabs + ebx*4], MEM_BLOCK_RESERVED
        inc     ebx
        dec     eax
        jnz     .fill

  .second_nofill:
        sub     ecx, edx
        jz      .nothird
        or      cl, MEM_BLOCK_FREE
        mov     [page_tabs + ebx*4], ecx

  .nothird:
        mov     edx, [current_process]
        mov     ebx, [alloc_size]
        add     ebx, 0xFFF
        and     ebx, not 0xFFF
        add     [edx + PROC.mem_used], ebx

        lea     ecx, [edx + PROC.heap_lock]
        call    mutex_unlock

        mov     eax, [address]

        pop     edi
        pop     esi
        pop     ebx
        ret
endp

align 4
proc user_free stdcall, base:dword

        push    esi

        mov     esi, [base]
        test    esi, esi
        jz      .fail

        push    ebx

        mov     ebx, [current_process]
        lea     ecx, [ebx + PROC.heap_lock]
        call    mutex_lock

        xor     ebx, ebx
        shr     esi, 12
        mov     eax, [page_tabs + (esi-1)*4]
        test    al, MEM_BLOCK_USED
        jz      .cantfree
        test    al, MEM_BLOCK_DONT_FREE
        jnz     .cantfree

        and     eax, not 4095
        mov     ecx, eax
        or      al, MEM_BLOCK_FREE
        mov     [page_tabs + (esi-1)*4], eax
        sub     ecx, 4096
        mov     ebx, ecx
        shr     ecx, 12
        jz      .released
  .release:
        xor     eax, eax
        xchg    eax, [page_tabs + esi*4]
        test    al, 1
        jz      @F
        test    eax, PG_SHARED
        jnz     @F
        call    free_page
        mov     eax, esi
        shl     eax, 12
        invlpg  [eax]
  @@:
        inc     esi
        dec     ecx
        jnz     .release

 .released:
        push    edi

        mov     edx, [current_process]
        lea     ecx, [edx + PROC.heap_lock]
        mov     esi, dword [edx + PROC.heap_base]
        mov     edi, dword [edx + PROC.heap_top]
        sub     ebx, [edx + PROC.mem_used]
        neg     ebx
        mov     [edx + PROC.mem_used], ebx
        call    user_normalize
        pop     edi
 .exit:
        call    mutex_unlock

        xor     eax, eax
        inc     eax
        pop     ebx
        pop     esi
        ret

  .cantfree:
        mov     ecx, [current_process]
        lea     ecx, [ecx + PROC.heap_lock]
        jmp     .exit
  .fail:
        xor     eax, eax
        pop     esi
        ret
endp


align 4
proc user_unmap stdcall, base:dword, offset:dword, size:dword

        push    ebx

        mov     ebx, [base]             ; must be valid pointer
        test    ebx, ebx
        jz      .error

        mov     edx, [offset]           ; check offset
        add     edx, ebx                ; must be below 2Gb app limit
        js      .error

        shr     ebx, 12                 ; chek block attributes
        lea     ebx, [page_tabs + ebx*4]
        mov     eax, [ebx-4]            ; block attributes
        test    al, MEM_BLOCK_USED
        jz      .error
        test    al, MEM_BLOCK_DONT_FREE
        jnz     .error

        shr     edx, 12
        lea     edx, [page_tabs + edx*4]  ; unmap offset

        mov     ecx, [size]
        add     ecx, 4095
        shr     ecx, 12                 ; unmap size in pages

        shr     eax, 12                 ; block size  +  1 page
        lea     ebx, [ebx + eax*4-4]      ; block end ptr
        lea     eax, [edx + ecx*4]        ; unmap end ptr

        cmp     eax, ebx                ; check for overflow
        ja      .error

        mov     ebx, [offset]
        and     ebx, not 4095           ; is it required ?
        add     ebx, [base]

  .unmap:
        mov     eax, [edx]              ; get page addres
        test    al, 1                   ; page mapped ?
        jz      @F
        test    eax, PG_SHARED          ; page shared ?
        jnz     @F
        mov     dword[edx], MEM_BLOCK_RESERVED
                                        ; mark page as reserved
        invlpg  [ebx]                   ; when we start using
        call    free_page               ; empty c-o-w page instead this ?
  @@:
        add     ebx, 4096       ; PAGESIZE?
        add     edx, 4
        dec     ecx
        jnz     .unmap

        pop     ebx
        or      al, 1                   ; return non zero on success
        ret
.error:
        pop     ebx
        xor     eax, eax                ; something wrong
        ret
endp

align 4
user_normalize:
; in: esi=heap_base, edi=heap_top
; out: eax=0 <=> OK
; destroys: ebx,edx,esi,edi
        shr     esi, 12
        shr     edi, 12
@@:
        mov     eax, [page_tabs + esi*4]
        test    al, MEM_BLOCK_USED
        jz      .test_free
        shr     eax, 12
        add     esi, eax
        jmp     @B
.test_free:
        test    al, MEM_BLOCK_FREE
        jz      .err
        mov     edx, eax
        shr     edx, 12
        add     edx, esi
        cmp     edx, edi
        jae     .exit

        mov     ebx, [page_tabs + edx*4]
        test    bl, MEM_BLOCK_USED
        jz      .next_free

        shr     ebx, 12
        add     edx, ebx
        mov     esi, edx
        jmp     @B
.next_free:
        test    bl, MEM_BLOCK_FREE
        jz      .err
        and     dword[page_tabs + edx*4], 0
        add     eax, ebx
        and     eax, not 4095           ; not (PAGESIZE - 1)    ?
        or      eax, MEM_BLOCK_FREE
        mov     [page_tabs + esi*4], eax
        jmp     @B
.exit:
        xor     eax, eax
        inc     eax
        ret
.err:
        xor     eax, eax
        ret

user_realloc:
; in: eax = pointer, ebx = new size
; out: eax = new pointer or NULL
        test    eax, eax
        jnz     @f
; realloc(NULL,sz) - same as malloc(sz)
        push    ebx
        call    user_alloc
        ret
@@:
        push    ecx edx

        push    eax
        mov     ecx, [current_process]
        lea     ecx, [ecx + PROC.heap_lock]
        call    mutex_lock
        pop     eax

        lea     ecx, [eax - 0x1000]
        shr     ecx, 12
        mov     edx, [page_tabs + ecx*4]
        test    dl, MEM_BLOCK_USED
        jnz     @f
; attempt to realloc invalid pointer
.ret0:
        mov     ecx, [current_process]
        lea     ecx, [ecx + PROC.heap_lock]
        call    mutex_unlock

        pop     edx ecx
        xor     eax, eax
        ret
@@:
        test    dl, MEM_BLOCK_DONT_FREE
        jnz     .ret0
        add     ebx, 0x1FFF
        shr     edx, 12
        shr     ebx, 12
; edx = allocated size, ebx = new size
        add     edx, ecx
        add     ebx, ecx
        cmp     edx, ebx
        jb      .realloc_add
; release part of allocated memory
.loop:
        cmp     edx, ebx
        jz      .release_done
        dec     edx
        xor     eax, eax
        xchg    eax, [page_tabs + edx*4]
        test    al, 1
        jz      .loop
        call    free_page
        mov     eax, edx
        shl     eax, 12
        invlpg  [eax]
        jmp     .loop
.release_done:
        sub     ebx, ecx
        cmp     ebx, 1
        jnz     .nofreeall
        mov     eax, [page_tabs + ecx*4]
        and     eax, not 0xFFF
        mov     edx, [current_process]
        mov     ebx, [edx + PROC.mem_used]
        sub     ebx, eax
        add     ebx, 0x1000
        or      al, MEM_BLOCK_FREE
        mov     [page_tabs + ecx*4], eax
        push    esi edi
        mov     esi, [edx + PROC.heap_base]
        mov     edi, [edx + PROC.heap_top]
        mov     [edx + PROC.mem_used], ebx
        call    user_normalize
        pop     edi esi
        jmp     .ret0   ; all freed
.nofreeall:
        sub     edx, ecx
        shl     ebx, 12
        or      ebx, MEM_BLOCK_USED
        xchg    [page_tabs + ecx*4], ebx
        shr     ebx, 12
        sub     ebx, edx
        push    ebx ecx edx
        mov     edx, [current_process]
        shl     ebx, 12
        sub     ebx, [edx + PROC.mem_used]
        neg     ebx
        mov     [edx + PROC.mem_used], ebx
        pop     edx ecx ebx
        lea     eax, [ecx + 1]
        shl     eax, 12
        push    eax
        add     ecx, edx
        lea     edx, [ecx + ebx]
        shl     ebx, 12
        jz      .ret
        push    esi
        mov     esi, [current_process]
        mov     esi, [esi + PROC.heap_top]
        shr     esi, 12
@@:
        cmp     edx, esi
        jae     .merge_done
        mov     eax, [page_tabs + edx*4]
        test    al, MEM_BLOCK_USED
        jnz     .merge_done
        and     dword [page_tabs + edx*4], 0
        shr     eax, 12
        add     edx, eax
        shl     eax, 12
        add     ebx, eax
        jmp     @b
.merge_done:
        pop     esi
        or      ebx, MEM_BLOCK_FREE
        mov     [page_tabs + ecx*4], ebx
.ret:
        mov     ecx, [current_process]
        lea     ecx, [ecx + PROC.heap_lock]
        call    mutex_unlock
        pop     eax edx ecx
        ret

.realloc_add:
; get some additional memory
        mov     eax, [current_process]
        mov     eax, [eax + PROC.heap_top]
        shr     eax, 12
        cmp     edx, eax
        jae     .cant_inplace
        mov     eax, [page_tabs + edx*4]
        test    al, MEM_BLOCK_FREE
        jz      .cant_inplace
        shr     eax, 12
        add     eax, edx
        sub     eax, ebx
        jb      .cant_inplace
        jz      @f
        shl     eax, 12
        or      al, MEM_BLOCK_FREE
        mov     [page_tabs + ebx*4], eax
@@:
        mov     eax, ebx
        sub     eax, ecx
        shl     eax, 12
        or      al, MEM_BLOCK_USED
        mov     [page_tabs + ecx*4], eax
        lea     eax, [ecx + 1]
        shl     eax, 12
        push    eax
        push    edi
        lea     edi, [page_tabs + edx*4]
        mov     eax, 2
        sub     ebx, edx
        mov     ecx, ebx
        cld
        rep stosd
        pop     edi
        mov     edx, [current_process]
        shl     ebx, 12
        add     [edx + PROC.mem_used], ebx

        mov     ecx, [current_process]
        lea     ecx, [ecx + PROC.heap_lock]
        call    mutex_unlock
        pop     eax edx ecx
        ret

.cant_inplace:
        push    esi edi
        mov     eax, [current_process]
        mov     esi, [eax + PROC.heap_base]
        mov     edi, [eax + PROC.heap_top]
        shr     esi, 12
        shr     edi, 12
        sub     ebx, ecx
.find_place:
        cmp     esi, edi
        jae     .place_not_found
        mov     eax, [page_tabs + esi*4]
        test    al, MEM_BLOCK_FREE
        jz      .next_place
        shr     eax, 12
        cmp     eax, ebx
        jae     .place_found
        add     esi, eax
        jmp     .find_place
.next_place:
        shr     eax, 12
        add     esi, eax
        jmp     .find_place
.place_not_found:
        pop     edi esi
        jmp     .ret0
.place_found:
        sub     eax, ebx
        jz      @f
        push    esi
        add     esi, ebx
        shl     eax, 12
        or      al, MEM_BLOCK_FREE
        mov     [page_tabs + esi*4], eax
        pop     esi
@@:
        mov     eax, ebx
        shl     eax, 12
        or      al, MEM_BLOCK_USED
        mov     [page_tabs + esi*4], eax
        inc     esi
        mov     eax, esi
        shl     eax, 12
        push    eax
        mov     eax, [page_tabs + ecx*4]
        and     eax, not 0xFFF
        or      al, MEM_BLOCK_FREE
        sub     edx, ecx
        mov     [page_tabs + ecx*4], eax
        inc     ecx
        dec     ebx
        dec     edx
        jz      .no
@@:
        xor     eax, eax
        xchg    eax, [page_tabs + ecx*4]
        mov     [page_tabs + esi*4], eax
        mov     eax, ecx
        shl     eax, 12
        invlpg  [eax]
        inc     esi
        inc     ecx
        dec     ebx
        dec     edx
        jnz     @b
.no:
        push    ebx
        mov     edx, [current_process]
        shl     ebx, 12
        add     [edx + PROC.mem_used], ebx
        pop     ebx
@@:
        mov     dword [page_tabs + esi*4], MEM_BLOCK_RESERVED
        inc     esi
        dec     ebx
        jnz     @b

        mov     ecx, [current_process]
        lea     ecx, [ecx + PROC.heap_lock]
        call    mutex_unlock
        pop     eax edi esi edx ecx
        ret



;;;;;;;;;;;;;;      SHARED MEMORY     ;;;;;;;;;;;;;;;;;


; param
;  eax= shm_map object

align 4
destroy_smap:

        pushfd
        cli

        push    esi
        push    edi

        mov     edi, eax
        mov     esi, [eax + SMAP.parent]
        test    esi, esi
        jz      .done

        lock dec [esi + SMEM.refcount]
        jnz     .done

        mov     ecx, [esi + SMEM.bk]
        mov     edx, [esi + SMEM.fd]

        mov     [ecx + SMEM.fd], edx
        mov     [edx + SMEM.bk], ecx

        stdcall kernel_free, [esi + SMEM.base]
        mov     eax, esi
        call    free
.done:
        mov     eax, edi
        call    destroy_kernel_object

        pop     edi
        pop     esi
        popfd

        ret

E_NOTFOUND      =  5
E_ACCESS        = 10
E_NOMEM         = 30
E_PARAM         = 33

SHM_READ        = 0
SHM_WRITE       = 1

SHM_ACCESS_MASK = 3

SHM_OPEN        = 0 shl 2
SHM_OPEN_ALWAYS = 1 shl 2
SHM_CREATE      = 2 shl 2

SHM_OPEN_MASK   = 3 shl 2

align 4
proc shmem_open stdcall name:dword, size:dword, access:dword
        locals
           action         dd ?
           owner_access   dd ?
           mapped         dd ?
        endl

        push    ebx
        push    esi
        push    edi

        mov     [mapped], 0
        mov     [owner_access], 0

        pushfd                         ;mutex required
        cli

        mov     eax, [access]
        and     eax, SHM_OPEN_MASK
        mov     [action], eax

        mov     ebx, [name]
        test    ebx, ebx
        mov     edx, E_PARAM
        jz      .fail

        mov     esi, [shmem_list.fd]
align 4
@@:
        cmp     esi, shmem_list
        je      .not_found

        lea     edx, [esi + SMEM.name]; link , base, size
        stdcall strncmp, edx, ebx, 32
        test    eax, eax
        je      .found

        mov     esi, [esi + SMEM.fd]
        jmp     @B

.not_found:
        mov     eax, [action]

        cmp     eax, SHM_OPEN
        mov     edx, E_NOTFOUND
        je      .fail

        cmp     eax, SHM_CREATE
        mov     edx, E_PARAM
        je      .create_shm

        cmp     eax, SHM_OPEN_ALWAYS
        jne     .fail

.create_shm:

        mov     ecx, [size]
        test    ecx, ecx
        jz      .fail

        add     ecx, 4095
        and     ecx, -4096
        mov     [size], ecx

        mov     eax, sizeof.SMEM
        call    malloc
        test    eax, eax
        mov     esi, eax
        mov     edx, E_NOMEM
        jz      .fail

        stdcall kernel_alloc, [size]
        test    eax, eax
        mov     [mapped], eax
        mov     edx, E_NOMEM
        jz      .cleanup

        mov     ecx, [size]
        mov     edx, [access]
        and     edx, SHM_ACCESS_MASK

        mov     [esi + SMEM.base], eax
        mov     [esi + SMEM.size], ecx
        mov     [esi + SMEM.access], edx
        mov     [esi + SMEM.refcount], 0
        mov     [esi + SMEM.name + 28], 0

        lea     eax, [esi + SMEM.name]
        stdcall strncpy, eax, [name], 31

        mov     eax, [shmem_list.fd]
        mov     [esi + SMEM.bk], shmem_list
        mov     [esi + SMEM.fd], eax

        mov     [eax + SMEM.bk], esi
        mov     [shmem_list.fd], esi

        mov     [action], SHM_OPEN
        mov     [owner_access], SHM_WRITE

.found:
        mov     eax, [action]

        cmp     eax, SHM_CREATE
        mov     edx, E_ACCESS
        je      .exit

        cmp     eax, SHM_OPEN
        mov     edx, E_PARAM
        je      .create_map

        cmp     eax, SHM_OPEN_ALWAYS
        jne     .fail

.create_map:

        mov     eax, [access]
        and     eax, SHM_ACCESS_MASK
        cmp     eax, [esi + SMEM.access]
        mov     [access], eax
        mov     edx, E_ACCESS
        ja      .fail

        mov     ebx, [CURRENT_TASK]
        shl     ebx, 5
        mov     ebx, [CURRENT_TASK + ebx + 4]
        mov     eax, sizeof.SMAP

        call    create_kernel_object
        test    eax, eax
        mov     edi, eax
        mov     edx, E_NOMEM
        jz      .fail

        inc     [esi + SMEM.refcount]

        mov     [edi + SMAP.magic], 'SMAP'
        mov     [edi + SMAP.destroy], destroy_smap
        mov     [edi + SMAP.parent], esi
        mov     [edi + SMAP.base], 0

        stdcall user_alloc, [esi + SMEM.size]
        test    eax, eax
        mov     [mapped], eax
        mov     edx, E_NOMEM
        jz      .cleanup2

        mov     [edi + SMAP.base], eax

        mov     ecx, [esi + SMEM.size]
        mov     [size], ecx

        shr     ecx, 12
        shr     eax, 10

        mov     esi, [esi + SMEM.base]
        shr     esi, 10
        lea     edi, [page_tabs + eax]
        add     esi, page_tabs

        mov     edx, [access]
        or      edx, [owner_access]
        shl     edx, 1
        or      edx, PG_SHARED + PG_UR
@@:
        lodsd
        and     eax, 0xFFFFF000
        or      eax, edx
        stosd
        loop    @B

        xor     edx, edx

        cmp     [owner_access], 0
        jne     .fail
.exit:
        mov     edx, [size]
.fail:
        mov     eax, [mapped]

        popfd
        pop     edi
        pop     esi
        pop     ebx
        ret
.cleanup:
        mov     [size], edx
        mov     eax, esi
        call    free
        jmp     .exit

.cleanup2:
        mov     [size], edx
        mov     eax, edi
        call    destroy_smap
        jmp     .exit
endp

align 4
proc shmem_close stdcall, name:dword

        mov     eax, [name]
        test    eax, eax
        jz      .fail

        push    esi
        push    edi
        pushfd
        cli

        mov     esi, [current_slot]
        add     esi, APP_OBJ_OFFSET
.next:
        mov     eax, [esi + APPOBJ.fd]
        test    eax, eax
        jz      @F

        cmp     eax, esi
        mov     esi, eax
        je      @F

        cmp     [eax + SMAP.magic], 'SMAP'
        jne     .next

        mov     edi, [eax + SMAP.parent]
        test    edi, edi
        jz      .next

        lea     edi, [edi + SMEM.name]
        stdcall strncmp, [name], edi, 32
        test    eax, eax
        jne     .next

        stdcall user_free, [esi + SMAP.base]

        mov     eax, esi
        call    [esi + APPOBJ.destroy]
@@:
        popfd
        pop     edi
        pop     esi
.fail:
        ret
endp



proc    user_ring stdcall, size:dword

locals
        virt_ptr        dd ?
        phys_ptr        dd ?
        num_pages       dd ?
endl

; Size must be an exact multiple of pagesize
        mov     eax, [size]
        test    eax, PAGE_SIZE-1
        jnz     .exit

; We must have at least one complete page
        shr     eax, 12
        jz      .exit
        mov     [num_pages], eax

; Allocate double the virtual memory
        mov     eax, [size]
        shl     eax, 1
        jz      .exit
        stdcall user_alloc, eax
        test    eax, eax
        jz      .exit
        mov     [virt_ptr], eax

; Now allocate physical memory
        stdcall alloc_pages, [num_pages]
        test    eax, eax
        jz      .exit_free_virt
        mov     [phys_ptr], eax

; Map first half of virtual memory to physical memory
        push    ecx esi edi
        mov     ecx, [num_pages]
        mov     esi, [virt_ptr]
        mov     edi, [phys_ptr]
  .loop1:
        stdcall map_page, esi, edi, PG_UWR
        add     esi, PAGE_SIZE
        add     edi, PAGE_SIZE
        dec     ecx
        jnz     .loop1

; Map second half of virtual memory to same physical memory
        mov     ecx, [num_pages]
        mov     edi, [phys_ptr]
  .loop2:
        stdcall map_page, esi, edi, PG_UWR
        add     esi, PAGE_SIZE
        add     edi, PAGE_SIZE
        dec     ecx
        jnz     .loop2
        pop     edi esi ecx

        mov     eax, [virt_ptr]
        ret

  .exit_free_virt:
        stdcall user_free, [virt_ptr]

  .exit:
        xor     eax, eax
        ret

endp