; mem.asm
;
; Robert Ostling, 2002-02-02
;
;
; Contains functions to allocate and free memory blocks.
;
;
; Memory table entry
;
; start address		dword
; size of block		dword
; status		byte
;  0 = not used
;  1 = free
;  2 = used
;


%define _mem_asm 0

[bits 32]
[section .text]

global mem_init
global mem_alloc
global mem_free
global mem_alloc_dma_page
global mem_free_dma_page

%include "config.inc"

%include "mem.inc"

; mem_init
;
; initialized memory allocation tables.
;
; returns:
; all registers unchanged

mem_init:
		push	eax
		push	ecx
		push	edi

		mov	edi,mem_table		; let's fill the memory table
		xor	eax,eax			; with zeroes
		mov	ecx,mem_default_table_size*9
		rep	stosd

		mov	edi,mem_table

		mov	eax,dword[mem_high_start]
		mov	dword[edi],eax		; start of first memory block
		mov	ecx,dword[mem_high_end]
		sub	ecx,eax			; size = end - start (yay!)
		mov	dword[edi+4],ecx	; size of first memory block
		mov	byte[edi+8],MEM_STATUS_FREE

		mov	dword[mem_table_max],9

		pop	edi
		pop	ecx
		pop	eax
		ret



; mem_alloc
;
; allocates memory
;
; eax = requested size
;
; returns:
; esi = pointer to allocated memory

mem_alloc:
		cli
		push	ecx
		push	edx

		mov	ecx,mem_table
.try_block:
		; now make sure the block is free, and large enough
		; first of all, is it free?
		cmp	byte[ecx+8],MEM_STATUS_FREE
		jnz	.next_block		; no, try with next block
		cmp	dword[ecx+4],eax	; is the block large enough?
		jb	.next_block		; nope, try next block
		jnz	.resize			; if the block is too big, resize it

		; since the block is just the size we want, just mark it as used
		mov	byte[ecx+8],MEM_STATUS_USED
		mov	esi,dword[ecx]
		jmp	short .quit

.resize:
		mov	edx,mem_table
		add	edx,dword[mem_table_max]; get an unused memory table entry
		cmp	edx,mem_table+mem_default_table_size
		jnb	.error			; if nothing found, quit

		; now subtract the amount of memory we want from the free block
		sub	dword[ecx+4],eax

		mov	esi,dword[ecx]		; the start of the free block
		mov	dword[edx],esi		; the start of the new block
		add	dword[ecx],eax		; add size of requested amount if memory to the
						; start of the old block.
		mov	dword[edx+4],eax	; set size of the new block.
		mov	byte[edx+8],MEM_STATUS_USED
						; mark it as used
		add	dword[mem_table_max],9	; make sure to resize mem_table
.quit:
		pop	edx
		pop	ecx
		sti
		ret

.error:
		xor	esi,esi
		jmp	short .quit

.next_block:
		add	ecx,byte 9
		cmp	ecx,(mem_default_table_size*9)+mem_table
						; check if we've reached last block
		jb	.try_block		; nope, check this block
		xor	esi,esi			; return NULL
		jmp	short .quit


; mem_free
;
; frees the allocated memory block at eax
;
; eax = memory offset to free
;
; returns:
; eax = 0 if everything's OK

mem_free:
		cli
		push	ecx
		push	edx

		mov	ecx,mem_table
.try_next:
		cmp	dword[ecx],eax		; is this the mem_table entry we're looking for?
		jnz	.nope

		; now we've found the block to free, simply mark is as 'free', and defrag. the table
		mov	byte[ecx+8],MEM_STATUS_FREE
		call	mem_defrag
		xor	eax,eax			; return 0 (OK)
		jmp	.quit
.nope:
		add	ecx,9			; go to the next mem_table entry
		mov	edx,dword[mem_table_max]
		add	edx,mem_table		; calculate the end of the memory table
		cmp	ecx,edx			; have we reached the end?
		jb	.try_next		; no, try this entry too

		mov	al,1			; make eax non-zero
.quit:
		pop	edx
		pop	ecx
		sti
		ret

; mem_defrag
;
; used by mem_free to defragment the memory table
;
; returns:
; all registers unchanged

mem_defrag:
		push	eax
		push	ecx
		push	edx
		push	ebx
		push	esi
		push	edi

.defrag_more:
		xor	edi,edi			; edi = number of defragmentations made this iteration
		mov	ebx,mem_table		; ebx points to the table entry to check
.main_loop:
		cmp	byte[ebx+8],MEM_STATUS_FREE
		jnz	near .main_next		; if the block is not free, try next block
		mov	ecx,mem_table		; ecx points to the table entry to check against
.check_loop:
		cmp	byte[ecx+8],MEM_STATUS_FREE
		jnz	.check_next		; if this block was not free, try the next

		mov	eax,dword[ecx]
		add	eax,dword[ecx+4]	; end of block to check against
		cmp	eax,dword[ebx]		; compare to start of first block
		jnz	.not_case0		; if not equal, try next combination
.case0:
		mov	eax,dword[ebx+4]	; size of first block
		add	dword[ecx+4],eax	; add to size of second block

		inc	edi

		; the 'ebx' block is now not used anymore, move the last block of the mem_table to its place
		; and decrease mem_table_max with 9

		mov	esi,mem_table
		sub	dword[mem_table_max],9
		add	esi,dword[mem_table_max]

		mov	eax,dword[esi]
		mov	dword[ebx],eax
		mov	eax,dword[esi+4]
		mov	dword[ebx+4],eax
		mov	al,byte[esi+8]
		mov	byte[ebx+8],al

		jmp	short .main_next
.not_case0:
		mov	eax,dword[ebx]
		add	eax,dword[ebx+4]	; end of block to check
		cmp	eax,dword[ecx]		; compare to start of first block to check against
		jnz	.not_case1		; if not equal, try next block
.case1:

		mov	eax,dword[ecx+4]	; size of first block
		add	dword[ebx+4],eax	; add to size of second block

		inc	edi

		; the 'ecx' block is now not used anymore, move the last block of the mem_table to its place
		; and decrease mem_table_max with 9

		mov	esi,mem_table
		sub	dword[mem_table_max],9
		add	esi,dword[mem_table_max]

		mov	eax,dword[esi]
		mov	dword[ecx],eax
		mov	eax,dword[esi+4]
		mov	dword[ecx+4],eax
		mov	al,byte[esi+8]
		mov	byte[ecx+8],al

		jmp	short .main_next
.not_case1:
.check_next:
		add	ecx,9			; go to next mem_table entry
		cmp	ecx,ebx			; compare to the 'ebx' block
		jz	.check_next		; if they're the same, go to next block
		mov	eax,dword[mem_table_max]
		add	eax,mem_table		; compare its address to end of mem_table
		cmp	ecx,eax			; if below, contunie searching
		jb	.check_loop		; else, just continue

.main_next:
		add	ebx,9			; go to next mem_table entry
		mov	eax,dword[mem_table_max]
		add	eax,mem_table		; compare its address to end of mem_table
		cmp	ebx,eax			; if below, contunie searching
		jb	.main_loop		; else, just continue

		or	edi,edi			; was there any fragmentation?
		jnz	.defrag_more		; yup, let's go for another round

.quit:
		pop	edi
		pop	esi
		pop	ebx
		pop	edx
		pop	ecx
		pop	eax

		ret



; mem_alloc_dma_page
;
; Allocate a 64L DMA page (yes, I know.. this is a waste of memory)
;
; returns:
; eax = 0 if OK, else 1
; esi = page number

mem_alloc_dma_page:
		mov	esi,mem_dma_pages
		mov	ecx,0x0a
.try_next:
		lodsb
		or	al,al
		jnz	.found
		loop	.try_next

		mov	eax,1
		ret
.found:
		movzx	esi,al
		xor	eax,eax
		ret



; mem_free_dma_page
;
; Free a DMA page.
;
; returns:
; All register unchanged

mem_free_dma_page:
		push	esi
		cmp	esi,0x0a
		jnb	.quit

		add	esi,mem_dma_pages
		mov	byte[esi],1
.quit:
		pop	esi
		ret



[section .data]

mem_table_size	dd	mem_default_table_size
mem_high_start	dd	mem_default_high_start
mem_high_end	dd	mem_default_high_end
mem_low_start	dd	mem_default_low_start
mem_low_end	dd	mem_default_low_end

mem_dma_pages:
		db	0			; page 0 is reserved
		db	1,1,1,1,1,1,1,1		; page 1-8 are free
		db	0			; page 9 is reserved


[section .bss]

mem_table_max	resd	1			; size in bytes of what's currently used in mem_table
mem_table	resb	mem_default_table_size * 9
