/*
 * ldrec.S86  -  load record handling 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: ldrec.S86,v 1.5 2007/01/06 18:30:51 gkminix Exp $
 *
 *====================================================================
 */


#include <common.i86>
#include <netboot.i86>

.file __FILE__
.line __LINE__



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

	.text

	.extern	strlen			# define external symbols
	.extern	getext
	.extern	getmem

	.global	ldrini			# define global symbols
	.global	ldrfnd
	.global	ldradr
	.global	ldrlen



/*
 *====================================================================
 *
 * Initialize load record handling
 * Input:  FS:DI -  Pointer to boot image header
 *         DX    -  Offset to vendor magic ID string
 * Output: FS:DI -  Pointer to load record for this boot loader
 *         SI    -  Offset to error message, zero if none
 * Changed registers: EAX, BX, CX, DX, SI
 */
ldrini:	mov	si,offset bmerr
	cmp	dword ptr fs:boot_hd_magic[di],NBHEADER_MAGIC
	jne	9f			# compare boot rom magic numbers
	mov	word ptr [header + 0],di
	mov	word ptr [header + 2],fs

	mov	si,dx			# determine length of vendor
	call	strlen			# magic ID string
	add	cx,0x0003		# convert length into number of
	shr	cx,2			# dwords

	movzx	dx,byte ptr fs:boot_hd_length[di]
	shr	dx,4
	cmp	cx,dx			# check vendor ID size
	je	2f
1:	mov	si,offset vmerr		# set error message
	jmp	9f

2:	xor	bx,bx
	shl	cx,2
3:	mov	al,[bx + si]		# check vendor ID
	or	al,al
	jz	4f			# vendor ID ok, continue
	cmp	al,fs:boot_hd_vendor[bx + di]
	jne	1b
	inc	bx			# continue with next byte
	loop	3b

4:	call	getext			# get amount of extended memory
	or	eax,eax			# check if extended memory is
	jz	5f			# available
	shl	eax,10
	add	eax,0x00100000		# add amount of base memory
	jmp	6f
5:	call	getmem			# get amount of base memory
	shl	eax,10
6:	mov	[highadr],eax		# save address of last byte

	mov	si,offset lderr
	mov	al,LOADER_TAG
	call	ldrfnd			# find load record for this
	jc	9f			# program
	call	ldradr			# get linear address of loader
	jc	9f
	test	eax,0xFFF0000F		# has to be on a lower segment
	jnz	9f			# boundary
	shr	eax,4
	mov	bx,cs			# has to be our own segment
	cmp	ax,bx
	jne	9f
	xor	si,si			# return without error
9:	ret



/*
 *====================================================================
 *
 * Find a load record in the boot header.
 * Input:  AL    -  ID number of requested load record
 * Output: FS:DI -  Pointer to load record
 *         Carry flag set if load record not found
 * Changed registers: AX, DI, FS
 */
ldrfnd:	push	cx
	mov	ch,al
	lfs	di,[header]			# examine boot image header
	mov	ax,fs
	or	ax,di				# check if initialized
	jz	2f
	call	ldrlen				# get the pointer to first
	add	di,ax				# load record
1:	cmp	ch,fs:boot_ld_tag1[di]		# is it the desired one ?
	je	3f				# check if its the last record
	test	byte ptr fs:boot_ld_flags[di],BOOT_FLAG_EOF
	jnz	2f
	call	ldrlen
	add	di,ax
	jmp	1b

2:	stc					# couldnt find desired record
3:	pop	cx
	ret



/*
 *====================================================================
 *
 * Compute the length of a load record.
 * Input:  FS:DI -  Pointer to load record
 * Output: AX    -  Length of load record
 * Changed registers: AX
 */
ldrlen:	cmp	di,word ptr [header + 0]
	je	1f			# check if boot header or load record
	mov 	al,fs:boot_ld_length[di]
	jmp	2f

1:	mov	al,fs:boot_hd_length[di]
2:	mov 	ah,al
	shr	ah,4
	and	ax,0x0F0F		# compute the total length in
	add	al,ah			# bytes from the length of the
	xor	ah,ah			# record and that of the vendor
	shl	ax,2			# information.
	ret



/*
 *====================================================================
 *
 * Return the linear address of a load segment.
 * Input:  FS:DI -  Pointer to load record
 * Output: EAX   -  Linear address of load segment
 *         Carry flag set if error
 * Changed registers: EAX
 */
ldradr:	mov	al,fs:boot_ld_flags[di]
	test	al,BOOT_FLAG_B0		# check if we can compute
	jnz	2f			# the address directly

	test	al,BOOT_FLAG_B1
	jnz	1f
	mov	eax,fs:boot_ld_addr[di]	# get load address from load
	clc				# record (absolute address)
	jmp	9f

1:	mov	eax,[highadr]		# load record address is rela-
	sub	eax,fs:boot_ld_addr[di]	# tive to end of memory
	jmp	9f			# carry flag is set on error

/*
 * The load address in the load record is relative to the address of
 * the last record. However, the address of the last record can also
 * be relative to its last record. Therefore we have to go through
 * the whole list of records to determine each records linear address.
 */

2:	push	ebx
	push	ecx
	push	edx
	push	edi
	push	si
	mov	si,di			# save pointer to load record

	movzx32	edx,word ptr [header + 2]
	movzx32	edi,word ptr [header + 0]
	shl	edx,4
	add	edx,edi			# edx contains the linear
	mov	ebx,NBHEADER_SIZE	# address of the last entry
3:	call	ldrlen
	add	di,ax
	mov	ecx,fs:boot_ld_mlength[di]
	mov	al,fs:boot_ld_flags[di]
	test	al,BOOT_FLAG_B0		# check if address is relative
	jz	5f			# to last record

	test	al,BOOT_FLAG_B1
	jnz	4f
	add	edx,ebx			# add current address to end
	add	edx,fs:boot_ld_addr[di]	# of previous record
	jc	8f
	jmp	6f

4:	sub	edx,fs:boot_ld_addr[di]	# subtract current address
	jc 	8f			# from start of previous
	jmp	6f			# record

5:	mov	edx,fs:boot_ld_addr[di]
	test	al,BOOT_FLAG_B1
	jz	6f
	mov	edx,[highadr]		# subtract current address
	sub	edx,fs:boot_ld_addr[di]	# from end of memory
	jc	8f

6:	cmp	si,di			# check if we found our record
	je	7f
	mov	ebx,ecx
	test	al,BOOT_FLAG_EOF
	jz	3b
	stc				# if end of records reached then
	jmp	8f			# our record is not in the list

7:	mov	eax,edx			# return load record address
8:	mov	di,si			# restore all registers
	pop	si
	pop	edi
	pop	edx
	pop	ecx
	pop	ebx
9:	ret




/*
 *====================================================================
 *
 * String and constants definitions
 */
	_rodata


/*
 * Error messages
 */
bmerr:	.asciz	"Invalid boot header magic number"
vmerr:	.asciz	"Invalid vendor magic ID"
lderr:	.asciz	"Invalid load record information"




/*
 *====================================================================
 *
 * Variable definitions
 */
	.bss

	.global	header			# define global symbols

	.lcomm	header,4		# pointer to boot header
	.lcomm	highadr,4		# highest memory address




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

