/*
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  isr.S86
 * Purpose: UNDI interrupt service routines
 * Entries: read_isr, set_isr, rst_isr
 *
 **************************************************************************
 *
 * Copyright (C) 2003-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: isr.S86,v 1.12 2007/01/06 18:30:47 gkminix Exp $
 *
 **************************************************************************
 */

#include <common.i86>
#include <romconfig.i86>
#include <pxe/general.i86>
#include <pxe/entry.i86>
#include <pxe/undi.i86>
#include <net/ethernet.i86>
#include <system/i8259.i86>

.file __FILE__
.line __LINE__



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


	.global	_read_isr		# define entry points
	.global	_set_isr
	.global	_rst_isr


/*
 * External functions and data within text segment
 */

	.extern	rmdata
	.extern	pmdata




/*
 **************************************************************************
 *
 * Receive a packet from UNDI
 * Input:  None
 * Output: number of bytes copied
 * Registers changed: (E)AX
 */
_read_isr:

	enter	(isr_size + 2),0

#define isrbuf	(isr_size + 0)		/* offset to local ISR structure */
#define isrnum	(isr_size + 2)		/* offset to number of bytes read */

	push	bx
	push	cx
	push	dx
	push	es
	push	di

/*
 * Check that we have to process a receive interrupt by calling UNDI ISR.
 */

	mov	ax,UNDI_ISR_IN_PROCESS
1:	lea	bx,[bp - isr_size]
	call	doisr			# call UNDI
	jc	8f			# check for error
	cmp	ax,UNDI_ISR_OUT_DONE
	je	8f			# check if we are done
	cmp	ax,UNDI_ISR_OUT_BUSY
	je	8f			# check for recursive calling
	cmp	ax,UNDI_ISR_OUT_RECEIVE
	jne	2f			# we only want receive interrupts

/*
 * Check that we received a packet with a valid protocol. If not then read
 * the next packet and check again.
 */

	movzx	cx,byte ptr o_uisr_prottype[bp - isrbuf]
	cmp	cx,UNDI_PROT_IP
	jb	2f
	cmp	cx,UNDI_PROT_RARP
	jbe	3f
2:	mov	ax,UNDI_ISR_IN_GET_NEXT
	jmp	1b			# try next interrupt

8:	xor	ax,ax			# return with zero bytes
	jmp	9f

/*
 * We finally got a valid packet. First check that the packet will fit into
 * the local buffer. Then save the protocol type into the first two bytes
 * of the output buffer.
 */

3:	mov	bx,o_uisr_framelength[bp - isrbuf]
	mov	dx,o_uisr_headerlength[bp - isrbuf]
	sub	bx,dx
	jbe	2b			# the frame length has to be valid
	add	bx,2			# add the virtual header size
	mov	ax,[bufsize]		# get size of local buffer
	cmp	bx,ax			# check that receive buffer doesnt
	jbe	4f			# overflow local buffer
	mov	bx,ax
4:	mov	[bp - isrnum],bx	# save receive buffer size
	sub	bx,2

	mov	ax,ds
	mov	es,ax
	mov	di,[bufptr]		# get pointer to local buffer
	mov	es:[di],cx		# save protocol type
	add	di,2

/*
 * We can now cycle through all receive buffers and copy them into the
 * output buffer. At this point the registers are used as follows:
 *
 *	BX     -  remaining frame buffer size
 *	DX     -  header size
 *	ES:DI  -  pointer to local buffer
 */

5:	mov	cx,o_uisr_buflength[bp - isrbuf]
	sub	cx,dx			# skip media header
	sub	bx,cx			# compute remaining local buffer size
	jae	6f
	add	cx,bx			# should never exceed local buffer size
	xor	bx,bx

6:	cld
	push	ds
	push	si
	lds	si,o_uisr_frame[bp - isrbuf]
	add	si,dx
	rep	movsb			# copy buffer
	pop	si
	pop	ds
	or	bx,bx			# check if at end of frame
	jz	7f

	push	bx
	mov	ax,UNDI_ISR_IN_GET_NEXT
	lea	bx,[bp - isr_size]
	call	doisr			# call UNDI to get next buffer
	pop	bx
	jc	8b
	cmp	ax,UNDI_ISR_OUT_RECEIVE
	jne	8b			# we only accept receives now
	xor	dx,dx			# header size is now zero
	jmp	5b			# process next buffer

/*
 * We received the full frame now. However, since the local buffer can be
 * smaller than the frame we have to skip all following buffers until we
 * get UNDI_ISR_OUT_DONE from UNDI. This is important to allow UNDI to
 * reenable device interrupts again.
 */

7:	mov	ax,UNDI_ISR_IN_GET_NEXT
	lea	bx,[bp - isr_size]
	call	doisr			# call UNDI to recycle the last buffer
	jc	8b
	cmp	ax,UNDI_ISR_OUT_DONE
	jne	7b
	mov	ax,[bp - isrnum]	# return number of bytes received
9:	pop	di
	pop	es
	pop	dx
	pop	cx
	pop	bx
	leave
	ret

#undef isrbuf
#undef isrnum



/*
 **************************************************************************
 *
 * Call UNDI ISR routine. This function does not assume DS to be equal
 * to SS. However, if the parameter structure is located on the stack,
 * DS and SS have to be equal. This routine can also be called from within
 * an interrupt service routine.
 * Input:  AX    -  ISR function flag
 *         DS:BX -  Pointer to parameter structure
 * Output: AX    -  ISR function flag
 *         Carry flag set if error
 * Registers changed: AX
 */
doisr:

	mov	word ptr o_uisr_funcflag[bx],ax
	push	es
	push	ds			# push pointer to parameter structure
	push	bx
	push	UNDI_ISR		# push ISR function code
	les	bx,[pxeptr]
	lcall	es:o_ps_pxentry16[bx]	# call PXE API function
	pop	bx
	pop	bx			# restore BX
	pop	ds
	pop	es
	mov	ax,o_uisr_status[bx]	# check return status
	or	ax,ax
	stc
	jnz	9f
	mov	ax,o_uisr_funcflag[bx]	# return ISR function flag
	clc
9:	ret



/*
 **************************************************************************
 *
 * Hardware interrupt routine.
 * Input:  none
 * Output: none
 * Registers changed: none
 */
hwint:

/*
 * This routine should never get called recursively because hardware
 * interrupts should get turned off at the device level by the UNDI
 * driver. However, if this doesnt work for some reason, we count the
 * interrupt and return. The previous interrupt handler call will then
 * recycle this routine at the end.
 * Regarding the data segment we have to be careful here because it
 * is possible that we are already running in protected mode, but the
 * protected mode data segment selector has not yet been set. In this
 * case we just terminate interrupt processing. This should not happen,
 * but we need to make sure.
 */

	cli
	push	ds
	push	eax
	push	bx

	mov	bx,cs:[rmdata]		# set real mode data segment
	mov	eax,cr0
	test	ax,0x0001		# check protected mode flag
	jz	5f
	pushfd
	pop	eax
	bt	eax,17			# check virtual-8086 mode flag
	jc	5f
	mov	bx,cs:[pmdata]		# check if protected mode data segment
	or	bx,bx			# has been set, if not we are not even
	jz	9f			# able to access the hardware int no.
5:	mov	ds,bx
	inc	word ptr [intcnt]	# increment interrupt count
	cmp	word ptr [intcnt],1	# check if this is the first call
	jne	9f

/*
 * Call the UNDI driver to handle the interrupt, and check if its ours.
 */

1:	mov	ax,UNDI_ISR_IN_START
	mov	bx,offset params
	call	doisr
	jc	2f
	or	ax,ax			# check if interrupt is ours
	jz	4f

/*
 * If the interrupt was not ours we have to pass it down to the old interrupt
 * handler because PCI cards can have shared interrupts. This may invoke
 * the BIOS interrupt handler which will turn off the interrupt at the PIC.
 * Therefore, we have to reenable interrupts after return from the old handler.
 * In case we are running in protected mode, it is not possible to call the
 * old (i.e. real mode) handler, so we just ack the interrupt at the PIC.
 */

2:	mov	ax,word ptr [oldhwint + 2]	# check if an old handler is
	or	ax,ax				# available
	jz	4f
	mov	ax,ds
	cmp	ax,cs:[pmdata]		# check for protected mode
	je	4f

	cli
	pushf
	lcall	[oldhwint]		# call old handler

	push	dx
	mov	dx,MASTER_IMR
	mov	bx,[irqmask]		# get interrupt masks
	or	bl,bl			# check if its on master PIC
	jnz	5f
	mov	dx,SLAVE_IMR		# no, its on the slave
	mov	bl,bh
5:	in	al,dx			# read interrupt mask register
	test	al,bl			# check if it has been masked
	jz	6f
	not	bl
	and	al,bl			# unmask interrupt again
	out	dx,al
6:	pop	dx

/*
 * Terminate the hardware interrupt processing. If this routine got
 * called recursively, recycle it to handle the previous request.
 */

7:	sti				# allow repeated interrupt to get
	cli				# into the loop
	dec	word ptr [intcnt]
8:	jnz	1b
9:	pop	bx
	pop	eax
	pop	ds
	iret


/*
 * Send specific EOI since this interrupt was ours
 */

4:	cli
	mov	ax,[irqnum]
	cmp	ah,0xFF			# check if we have to care for
	je	3f			# a slave PIC
	xchg	al,ah
	add	al,CMD_S_EOI
	out	SLAVE_CMD,al		# send specific EOI to slave
	xchg	al,ah
3:	add	al,CMD_S_EOI
	out	MASTER_CMD,al		# send specific EOI to master
	jmp	7b			# always use polling mode



/*
 **************************************************************************
 *
 * Set our hardware interrupt
 * Input:  1. arg  -  hardware interrupt number (AX)
 *         2. arg  -  maximum size of local buffer
 *         3. arg  -  pointer to local buffer
 * Output: none
 * Registers changed: (E)AX
 */
_set_isr:

	enter	0,0

#define	blen	_ARG(0)
#define bptr	_ARG(1)

	push	bx
	push	cx
	push	dx
	push	si

/*
 * First save the address and size of the read buffer. Then check if the
 * UNDI driver supports hardware interrupts at all.
 */

	mov	bx,[bp + blen]		# get size of local buffer
	mov	[bufsize],bx
	mov	bx,[bp + bptr]		# get pointer to local buffer
	mov	[bufptr],bx

	or	ax,ax			# check if UNDI driver supports
	jz	5f			# hardware interrupts

/*
 * Compute the hardware interrupt vector offset, and the masks
 * necessary to program the interrupt controller. Keep IRQ mask
 * in BX, and offset to IRQ vector in SI.
 */

	mov	cx,ax
	mov	bx,1
	shl	bx,cl
	mov	[irqmask],bx		# set IRQ mask
	mov	ch,0xFF
	mov	si,MIN_MASTER_HWINT
	cmp	ax,PIC_IRQ_NUM
	jb	1f
	mov	ch,SLAVE_IRQ_NUM	# compute IRQ number and vector
	sub	cl,PIC_IRQ_NUM		# offset for slave
	xchg	cl,ch
	mov	si,MIN_SLAVE_HWINT - PIC_IRQ_NUM
1:	add	si,ax
	shl	si,2
	mov	[irqvect],si		# set IRQ number and vector offset
	mov	[irqnum],cx

/*
 * Save the old interrupt and then set the vector to our own interrupt
 * handler routine.
 */

	pushf
	cli
	push	es
	xor	ax,ax
	mov	es,ax
	mov	ax,cs
	shl	eax,16
	mov	ax,offset hwint
	xchg	es:[si],eax
	mov	[oldhwint],eax
	pop	es

/*
 * Save the old interrupt mask and unmask our hardware interrupt.
 */

	mov	dx,MASTER_IMR
	or	bl,bl			# check if its on master PIC
	jnz	2f
	mov	dx,SLAVE_IMR		# no, its on the slave
	mov	bl,bh
2:	in	al,dx			# read interrupt mask register
	mov	[oldmask],al
	not	bl
	and	al,bl			# unmask interrupt
	jmp	3f
3:	jmp	4f			# wait a little bit
4:	out	dx,al
	popf
	jmp	9f

/*
 * In case the UNDI driver does not support hardware interrupts, we
 * just set the IRQ number to zero.
 */

5:	mov	[irqnum],ax
9:	pop	si
	pop	dx
	pop	cx
	pop	bx
	leave
	ret

#undef blen
#undef bptr



/*
 **************************************************************************
 *
 * Reset our hardware interrupt to the original vector
 * Input:  none
 * Output: AX  -  non-zero if interrupt cant get reset
 * Registers changed: (E)AX
 */
_rst_isr:

	push	bx
	push	dx
	push	si
	mov	ax,[irqnum]		# check if interrupt set at all
	or	ax,ax
	jz	9f

/*
 * Set the interrupt mask of our hardware interrupt to its original value.
 */

	pushf
	cli
	mov	dx,MASTER_IMR
	mov	bx,[irqmask]		# get interrupt masks
	or	bl,bl			# check if its on master PIC
	jnz	1f
	mov	dx,SLAVE_IMR		# no, its on the slave
	mov	bl,bh
1:	in	al,dx			# read interrupt mask register
	and	bl,[oldmask]
	or	al,bl			# mask interrupt
	jmp	2f
2:	jmp	3f			# wait a little bit
3:	out	dx,al

/*
 * Restore the hardware interrupt vector. We cant reset it if the current
 * vector is not pointing to our own interrupt handler routine.
 */

	push	es
	xor	ax,ax
	mov	es,ax
	mov	si,[irqvect]
	mov	ax,cs
	shl	eax,16
	mov	ax,offset hwint		# check if current vector points to
	cmp	eax,es:[si]		# our int handler
	mov	ax,1
	jne	8f
	mov	eax,[oldhwint]
	mov	es:[si],eax		# restore old interrupt
	xor	ax,ax			# return without error
8:	pop	es
	popf
9:	pop	si
	pop	dx
	pop	bx
	ret




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

	.extern	pxeptr


	.lcomm	bufptr,2		# pointer to receive buffer
	.lcomm	bufsize,2		# size of receive buffer
	.lcomm	irqnum,2		# IRQ numbers for EOI
	.lcomm	irqmask,2		# IRQ masks
	.lcomm	irqvect,2		# offset to hardware IRQ vector
	.lcomm	intcnt,2		# count interrupt requests
	.lcomm	oldhwint,4		# old hardware interrupt vector
	.lcomm	oldmask,1		# old interrupt mask
	.lcomm	params,isr_size		# parameters for ISR




/*
 **************************************************************************
 */

	.end

