/*
 **************************************************************************
 *
 * Network driver interface for netboot bootrom
 *
 * Module:  protmode.S86
 * Purpose: Protected mode support for DOS drivers
 * Entries: switchreal, switchprot
 *
 **************************************************************************
 *
 * 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: protmode.S86,v 1.2 2007/01/06 18:30:48 gkminix Exp $
 *
 */


/*
 **************************************************************************
 *
 * Load assembler macros and other include files.
 *
 */
#include <common.i86>
#include <system/bootrom.i86>
#include <system/bios.i86>
#include <pxe/general.i86>
#include <pxe/entry.i86>
#include <pxe/undi.i86>

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * Start BSS segment.
 *
 */
	.bss

	.lcomm	oldgdt,6			# saved GDT descriptor
	.lcomm	oldidt,6			# saved IDT descriptor
	.lcomm	oldldt,2			# saved LDT descriptor
	.lcomm	oldtr,2				# saved task register
	.lcomm	oldcr0,4			# saved CR0 register
	.lcomm	oldcs,2				# saved CS register
	.lcomm	oldss,2				# saved SS register
	.lcomm	oldparams,4			# saved parameter pointer
	.lcomm	flatseg,4			# flat segment descriptor
	.lcomm	realss,2			# real mode stack segment
	.lcomm	paramsize,2			# size of UNDI parameters
	.lcomm	params,UNDI_MAX_PARAM_SIZE	# local UNDI parameter buffer




.line __LINE__
/*
 **************************************************************************
 *
 * Data segment:
 *
 */
	.data

/*
 * UNDI parameter structure sizes for all UNDI calls
 */

siztab:	.word	start_undi_size			# UNDI_START_UNDI
	.word	startup_size			# UNDI_STARTUP
	.word	cleanup_size			# UNDI_CLEANUP
	.word	initialize_size			# UNDI_INITIALIZE
	.word	reset_size			# UNDI_RESET_NIC
	.word	shutdown_size			# UNDI_SHUTDOWN
	.word	open_size			# UNDI_OPEN
	.word	close_size			# UNDI_CLOSE
	.word	transmit_size			# UNDI_TRANSMIT
	.word	setmcast_size			# UNDI_SET_MCAST_ADDR
	.word	setstation_size			# UNDI_SET_STATION_ADDR
	.word	setfilter_size			# UNDI_SET_PACKET_FILTER
	.word	getinfo_size			# UNDI_GET_INFORMATION
	.word	getstat_size			# UNDI_GET_STATISTICS
	.word	clearstat_size			# UNDI_CLEAR_STATISTICS
	.word	initdiag_size			# UNDI_INITIATE_DIAGS
	.word	forceint_size			# UNDI_FORCE_INTERRUPT
	.word	getmcast_size			# UNDI_GET_MCAST_ADDR
	.word	getnic_size			# UNDI_GET_NIC_TYPE
	.word	getiface_size			# UNDI_GET_IFACE_INFO
	.word	isr_size			# UNDI_ISR
	.word	stop_undi_size			# UNDI_STOP_UNDI
	.word	get_state_size			# UNDI_GET_STATE


/*
 * Real mode IDT selector
 */

newidt:	.word	0xFFFF				# real mode IDT limit
	.long	0				# real mode IDT address




/*
 **************************************************************************
 *
 * Start code segment.
 *
 */
	.text

	.global	switchreal
	.global	switchprot

	.extern	pxestruct



/*
 **************************************************************************
 *
 * Switch the CPU into real mode. This assumes that the application which
 * called UNDI did not modify the real mode environment. It also assumes
 * that we are running within a 16-bit segment.
 * Input:  BX     -  UNDI function code
 *         ES:DI  -  Pointer to original parameter structure
 * Output: ES:DI  -  Pointer to new parameter structure
 *         Carry flag set if error
 * Registers changed: EAX
 */

switchreal:

/*
 * First save all basic machine registers
 */

	sgdt	[oldgdt]		# save GDT
	sidt	[oldidt]		# save IDT
	sldt	[oldldt]		# save LDT
	str	[oldtr]			# save TR
	mov	eax,cr0
	and	eax,0x80000001		# isolate the protected mode and
	mov	[oldcr0],eax		# paging bits and then save CR0
	mov	[oldcs],cs		# save segment registers
	mov	[oldss],ss
	mov	word ptr [oldparams + 2],es
	mov	word ptr [oldparams + 0],di

/*
 * Compute the real segment for the stack. For this we need to access the
 * GDT. First we have to find a descriptor which allows us accessing the
 * whole memory, then we read the GDT entry corresponding to the current
 * SS value. If SS has not changed since the last call to this routine,
 * we can skip this test.
 */

	push	es
	test	word ptr [flatseg],0xFFFF
	jnz	8f			# check if we need to do this test

	push	bp
	mov	eax,0x0008
	push	eax			# start with this descriptor
	mov	bp,sp
4:	verr	word ptr [bp]		# check if we can read from this
	jnz	5f			# segment
	lsl	eax,dword ptr [bp]	# get segment limit
	jnz	5f
	cmp	eax,0xFFFFFFFF		# such a segment limit means that
	jne	5f			# the segment starts at addr 0
	pop	eax			# get segment descriptor
	pop	bp
	mov	[flatseg],eax
	jmp	7f

5:	add	word ptr [bp],2
	cmp	word ptr [bp],0x1000
	jb	4b
	pop	eax			# if at end of GDT, return with error
	pop	bp
9:	pop	es
	stc
	ret

8:	verr	word ptr [flatseg]	# quick check if descriptor is valid
	jnz	9b
	lsl	eax,dword ptr [flatseg]
	jnz	9b
	cmp	eax,0xFFFFFFFF
	jne	9b

7:	mov	ax,ss
	test	ax,0x0007		# check that stack segment is in GDT
	jnz	9b
	push	ebx
	mov	es,[flatseg]
	mov	ebx,[oldgdt + 2]	# get pointer to GDT
	movzx	eax,ax
	add	ebx,eax			# compute SS offset into GDT
	mov	ax,es:[bx+5]		# get segment base from GDT
	shl	eax,8
	mov	ax,es:[bx+4]
	shl	ax,8
	shl	eax,8
	mov	ax,es:[bx+2]
	pop	ebx
	test	eax,0xFFF0000F		# check if this can be converted into
	jnz	9b			# a real mode address
	shr	eax,4			# convert linear address into real
	mov	[realss],ax		# mode segment
	pop	es

/*
 * Copy the UNDI parameter structure into local storage. This is necessary
 * because this structure can be in a physical address which is not accessible
 * in real mode.
 */

	cld
	push	cx
	push	bx
	push	si
	shl	bx,1
	mov	cx,siztab[bx]
	mov	[paramsize],cx
	mov	si,offset params
1:	mov	al,es:[di]
	mov	[si],al
	inc	di
	inc	si
	loop	1b
	pop	si
	pop	bx
	pop	cx

/*
 * Now switch to real mode
 */

	pushf
	cli				# need to clear interrupts here
	mov	eax,cs:o_desc_addr[pxestruct + pxe_undi_text_sel]
	shr	eax,4
	push	ax
	push	offset 3f
	mov	eax,cr0
	and	eax,0x7FFFFFFE
	mov	cr0,eax
	jmp	2f
2:	mov	eax,cr3
	mov	cr3,eax
	lret				# jump to flush CS

/*
 * Setup segment registers and real mode IDT
 */

3:	mov	eax,cs:o_desc_addr[pxestruct + pxe_undi_data_sel]
	shr	eax,4
	mov	ds,ax			# set data segment register
	mov	es,ax			# set new parameter structure
	mov	di,offset params
	mov	ss,[realss]		# set stack segment register
	lidt	[newidt]		# set real mode IDT
	popf				# restore interrupt flag
	clc
	ret



/*
 **************************************************************************
 *
 * Switch the CPU back into protected mode
 * Input:  none
 * Output: ES:DI  -  Pointer to original parameter structure
 * Registers changed: EAX
 */

switchprot:

/*
 * Setup new environment and switch to protected mode
 */

	pushf
	cli				# interrupts have to be disabled
	lgdt	[oldgdt]		# load GDT
	lidt	[oldidt]		# load IDT
	push	[oldcs]
	push	offset 1f
	mov	eax,cr0
	and	eax,0x7FFFFFFE
	or	eax,[oldcr0]
	mov	cr0,eax			# switch to protected mode
	lret				# jump to flush CS

/*
 * Setup segment registers
 */

1:	mov	ax,word ptr cs:o_desc_segment[pxestruct + pxe_undi_data_sel]
	mov	ds,ax
	lldt	[oldldt]		# load LDT
	ltr	[oldtr]			# load task register
	mov	ss,[oldss]		# load old segment registers
	les	di,[oldparams]		# load old parameter pointer
	popf				# re-enable interrupts

/*
 * Copy the parameter structure back
 */

	cld
	push	si
	push	di
	push	cx
	mov	si,offset params
	mov	cx,[paramsize]
	rep	movsb
	pop	cx
	pop	di
	pop	si
	ret




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

