/*
 * memory.S86  -  memory access routines for netboot boot images
 *
 * Copyright (C) 2002-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: memory.S86,v 1.9 2007/01/06 18:30:51 gkminix Exp $
 *
 *====================================================================
 */


#include <common.i86>
#include <system/bios.i86>

.file __FILE__
.line __LINE__



/*
 *====================================================================
 *
 * Start the text segment and define all public entry points
 */

	.text

	.global	getext
	.global	getmem
	.global	movmem



/*
 *====================================================================
 *
 * Get amount of conventional memory in kB
 * Input:  none
 * Output: EAX   -  Amount of conventional memory
 * Changed registers: EAX
 */
getmem:	test	byte ptr [convini],0xFF
	jnz	1f			# check if already initialized
	int	INT_MEMORY		# get amount from BIOS
	mov	[convmem],ax		# save for later
	inc	byte ptr [convini]
	jmp	9f

1:	mov	ax,[convmem]		# load prestored value
9:	movzx32	eax,ax
	ret



/*
 *====================================================================
 *
 * Get amount of extended memory in kB
 * Input:  none
 * Output: EAX   -  Amount of extended memory
 * Changed registers: EAX
 */
getext:	test	byte ptr [extini],0xFF
	jnz	8f			# check if already initialized

/*
 * Getting the amount of extended memory is not as easy as it seems.
 * Different BIOS versions provide different interfaces, so we just
 * call one after the other. We have to use the same algorithm as the
 * bootrom here so that the boot rom and the boot loader agree on the
 * amount of extended memory installed.
 */

	push	ebx
	push	ecx
	push	edx
	mov	ax,0xE801		# try 16-bit version
	int	INT_MISC
	jc	3f
	mov	cx,ax			# some BIOS return incorrect
	or	cx,bx			# values
	jnz	1f
	mov	ax,cx
	mov	bx,dx
1:	movzx32	eax,ax			# clear upper word
	movzx32	ebx,bx
	shl	ebx,6			# multiply by 64
	add	eax,ebx			# and add to amount below 16M
	jmp	7f

3:	mov	ah,0x88			# try very old method
	clc
	int	INT_MISC		# call BIOS to get amount of
	jnc	4f			# extended memory in kB
	xor	ax,ax			# no extended memory found
4:	cwde
7:	mov	[extmem],eax		# save for later
	inc	byte ptr [extini]
	pop	edx
	pop	ecx
	pop	ebx
	jmp	9f

8:	mov	eax,[extmem]		# load prestored value
9:	ret



/*
 *====================================================================
 *
 * Move a memory block within memory, including high memory.
 * Input:  EAX   -  Linear source address
 *         EBX   -  Linear destination address
 *         ECX   -  Length of memory block
 * Output: Carry flag set if error
 * Changed registers: none
 */
movmem:	push	es
	push	ds			# save all registers that might
	pushad				# get mangled
	cmp	eax,ebx			# check if we have to copy anything
	je	8f			# at all
	jecxz	8f

	mov	edx,eax
	add	edx,ecx
	cmp	edx,0x00100000		# check if we have to copy into or
	jae	movext			# out of extended memory
	mov	edx,ebx
	add	edx,ecx
	cmp	edx,0x00100000
	jae	movext

	cmp	eax,ebx			# we have to copy within conventional
	jb	movtop			# memory, so check if we have to move
	jmp	movbot			# from top or from bottom

7:	stc				# return with error
	jmp	9f

8:	clc				# no error
9:	cld
	popad				# restore all registers
	pop	ds
	pop	es
	ret


/*
 * Move the memory within, into or out of extended memory. We have to use
 * the BIOS for this, and the BIOS always moves from bottom to top, which
 * makes for some limitations in the copying routine.
 */
movext:	mov	dx,ds			# prepare ES for int 0x15
	mov	es,dx

	mov	word ptr [rd_dstb],bx	# enter destination address into gdt
	mov	word ptr [rd_srcb],ax	# enter source address into gdt
	inc	ecx			# int 0x15 moves words
	and	ecx,0xFFFFFFFE

	mov	edx,eax
	add	edx,ecx			# check if the copying areas over-
	cmp	edx,ebx			# lap at all
	jbe	1f
	mov	edx,ebx
	add	edx,ecx
	cmp	edx,eax
	jbe	1f
	cmp	eax,ebx			# check if we have to move from bottom
	ja	1f			# to top
	mov	edx,ebx			# if the areas overlap, we can only
	sub	edx,eax			# move if the source and destination
	cmp	edx,0x00010000		# addresses differ by more than 64kB
	jb	7b

	mov	edx,ecx
	shr	edx,16
	shr	eax,16
	shr	ebx,16
	add	ax,dx			# move from the top downwards
	add	bx,dx
	mov	byte ptr [rd_dstb + 2],bl
	mov	byte ptr [rd_srcb + 2],al
	mov	byte ptr [rd_dstc],bh
	mov	byte ptr [rd_srcc],ah
	shr	cx,1
	jcxz	4f
	mov	si,offset rd_gdt
	mov	ax,0x8700		# have the BIOS move the memory
	int	INT_MISC
	jc	9b			# oops, something went wrong...
4:	or	dx,dx
5:	jz	8b
	sub	byte ptr [rd_srcb + 2],1
	sbb	byte ptr [rd_srcc],0	# decrement source address
	sub	byte ptr [rd_dstb + 2],1
	sbb	byte ptr [rd_dstc],0	# decrement destination address
	mov	cx,0x8000		# move in chunks of 64k/2 words
	mov	si,offset rd_gdt
	mov	ax,0x8700
	int	INT_MISC
	jc	9b			# oops, something went wrong...
	dec	dx			# check for next 64k block
	jmp	5b

1:	mov	edx,ecx
	shr	edx,16			# move from the bottom upwards
	shr	eax,16
	shr	ebx,16
	mov	byte ptr [rd_dstb + 2],bl
	mov	byte ptr [rd_srcb + 2],al
	mov	byte ptr [rd_dstc],bh
	mov	byte ptr [rd_srcc],ah
	or	dx,dx
2:	jz	3f
	push	cx
	mov	cx,0x8000		# move in chunks of 64k/2 words
	mov	si,offset rd_gdt
	mov	ax,0x8700
	int	INT_MISC
	pop	cx
	jc	9b			# oops, something went wrong...
	add	byte ptr [rd_srcb + 2],1
	adc	byte ptr [rd_srcc],0	# increment source address
	add	byte ptr [rd_dstb + 2],1
	adc	byte ptr [rd_dstc],0	# increment destination address
	dec	dx
	jmp	2b

3:	shr	cx,1
	jcxz	6f
	mov	si,offset rd_gdt
	mov	ax,0x8700		# have the BIOS move all the remain-
	int	INT_MISC		# ing bytes
6:	jmp	9b			# oops, something went wrong...


/*
 * Move within conventional memory from top to bottom
 */
movtop:	std
	mov	edx,ecx			# save size for later
	add	eax,ecx
	sub	eax,4			# convert linear source address into a
	mov	si,ax			# pointer to the highest byte to copy
	shr	eax,4
	and	ax,0xF000
	mov	ds,ax

	add	ebx,ecx
	sub	ebx,4			# convert linear destination address
	mov	di,bx			# into a pointer to the highest byte
	shr	ebx,4			# to copy
	and	bx,0xF000
	mov	es,bx
	jmp	5f			# compute highest available offsets

4:	mov	ax,si
	cmp	si,di			# we should not allow SI or DI to
	jbe	1f			# underflow, so we select the smaller
	mov	ax,di			# one of these as the initial count
1:	cmp	ax,cx
	jae	2f			# if the selected initial count exceeds
	mov	cx,ax			# the required count, set it to the
2:	movzx32	ebx,cx			# smaller value
	shr	cx,2
	rep
	movsd32				# now copy the next chunk
	mov	cx,bx
	and	cx,0x0003
	jz	6f
	add	si,3			# copy the bytes which havent been cared
	add	di,3			# for by the dword copy
	rep	movsb
6:	sub	edx,ebx			# check how many bytes left to copy
	jbe	8b			# nothing left

5:	mov	cx,0xFFFF
	sub	cx,si			# compute by how much we can increase
	and	cx,0xFFF0		# SI and DI without exceeding segment
	mov	bx,0xFFFF		# limits
	sub	bx,di
	and	bx,0xFFF0
	cmp	bx,cx			# select the smaller of the two value
	jbe	3f
	mov	bx,cx
3:	mov	cx,bx
	shr	bx,4
	mov	ax,ds
	sub	ax,bx
	jc	1f
	add	si,cx
	mov	ds,ax
1:	mov	ax,es			# this will care for DS:SI and ES:DI
	sub	ax,bx			# to still point to the same byte they
	jc	2f			# pointed before, but this time with
	add	di,cx			# much higher offsets allowing more room
	mov	es,ax			# to copy
2:	mov	ecx,edx
	jmp	4b


/*
 * Move from bottom to top within conventional memory
 */
movbot:	cld
	mov	edx,ecx			# save size for later
	mov	si,ax
	and	si,0x000F		# convert source and destination
	shr	eax,4			# addresses into pointers
	mov	ds,ax
	mov	di,bx
	and	di,0x000F
	shr	ebx,4
	mov	es,bx
	jmp	3f

1:	movzx32	ebx,cx			# save count for later
	shr	cx,2
	rep
	movsd32				# copy next chunk
	mov	cx,bx
	and	cx,0x0003
	jz	2f
	rep	movsb			# move remaining bytes
2:	sub	edx,ebx			# check how many bytes left to copy
	jbe	8b			# nothing left

	mov	ax,ds
	mov	cx,si
	shr	cx,4			# increase source segment
	add	ax,cx
	mov	ds,ax
	and	si,0x000F

	mov	ax,es
	mov	cx,di
	shr	cx,4			# increase destination segment
	add	ax,cx
	mov	es,ax
	and	di,0x000F

3:	mov	ecx,0x0000FFF0
	cmp	edx,ecx			# adjust count to maximum possible
	jae	1b
	mov	cx,dx
	jmp	1b




/*
 *====================================================================
 *
 * Data definitions
 */
	.data

/*
 * GDT to move a memory block around using the BIOS. It has to be
 * properly aligned because the CPU wants it this way.
 */
	.align	4

rd_gdt:	.word	0,0,0,0
	.word	0,0,0,0
rd_src:	.word	0xFFFF			# length
rd_srcb:.byte	0,0,0			# base
	.byte	0x93			# typebyte
	.byte	0			# limit16
rd_srcc:.byte	0			# base24
rd_dst:	.word	0xFFFF			# length
rd_dstb:.byte	0,0,0			# base
	.byte	0x93			# typebyte
	.byte	0			# limit16
rd_dstc:.byte	0			# base24
	.word	0,0,0,0			# BIOS CS
	.word	0,0,0,0			# BIOS DS




/*
 *====================================================================
 *
 * Define variables
 */
	.bss

	.global	extmem
	.global	convmem

	.lcomm	extmem,4	# amount of extended memory in kB
	.lcomm	convmem,2	# amount of conventional memory in kB
	.lcomm	extini,1	# flag if extmem initialized
	.lcomm	convini,1	# flag if convmem initialized




/*
 *====================================================================
 */
	.end

