/*
 **************************************************************************
 *
 * Network driver interface for netboot bootrom
 *
 * Module:  interrupt.S86
 * Purpose: Handle interrupt saving/restoring
 * Entries: intsave, intrestore, intchanged, intset, intreset,
 *          intstart, intstop, chkhwint, dohwint, endhwint
 *
 **************************************************************************
 *
 * Copyright (C) 1998-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: interrupt.S86,v 1.8 2007/01/06 18:30:48 gkminix Exp $
 *
 */


/*
 **************************************************************************
 *
 * Load assembler macros and other include files.
 *
 */
#include <common.i86>
#include <romconfig.i86>
#include <system/bios.i86>
#include <system/i8259.i86>

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * We have to deal with three types of interrupt vectors:
 *
 *   Type 0  -  Vector not used by network driver
 *   Type 1  -  Vector not permanently used by network driver. Such a vector
 *              is required during network driver initialization and shutdown,
 *              but not while the network driver is in operation. These include
 *              the DOS simulator interrupts, a packet driver software interrupt
 *              (which gets called directly by the packet driver interface in-
 *              stead of using INT) and the network card hardware interrupt
 *              (which also gets called directly by the driver interface on
 *              behalf of the bootrom kernel)
 *   Type 2  -  Vector will be used by network driver permanently. Usually
 *              there should be no vector of this type, but some network
 *              drivers require some redirected interrupts for example to
 *              handle timers etc.
 *
 * We have to save all vectors changed by the network driver in a seperate
 * array. The structure of this array is also defined here.
 *
 */
			.struct	0

inttab_type:		.struct	.+1	/* interrupt type */
inttab_vect:		.struct	.+1	/* interrupt vector number */
inttab_old:		.struct	.+4	/* old interrupt vector */
inttab_new:		.struct	.+4	/* new interrupt vector */
inttab_temp:		.struct	.+4	/* temporary interrupt vector */

#define	INTTAB_NUM	32
#define INTTAB_LENGTH	14
#define	INTTAB_SIZE	(INTTAB_LENGTH * INTTAB_NUM)




.line __LINE__
/*
 **************************************************************************
 *
 * Macros to preform certain actions on both PICs simultaneously. We implement
 * these as macros for speed. Space shouldnt be a consideration within a
 * interrupt service routine.
 *
 * Macro to generate a 5 microsecond delay between two data accesses to
 * a PIC. This delay is required per the EISA specification.
 *
 */
.macro	waitpic

	push	ax
	in	al,0x61			# these input instructions will
	in	al,0x61			# generate the necessary delay on
	in	al,0x61			# any system, even the fastest pentiums
	pop	ax

.endm


/*
 * Macro to send a command to both PICs
 */

.macro	sendcmd

	out	MASTER_CMD,al
	out	SLAVE_CMD,al

.endm


/*
 * Macro to read a data byte from both PICs
 */

.macro	readdata

	in	al,SLAVE_CMD
	mov	ah,al
	in	al,MASTER_CMD

.endm


/*
 * Macro to set the interrupt mask with both PICs
 */

.macro	setimr

	out	MASTER_IMR,al
	xchg	al,ah
	out	SLAVE_IMR,al

.endm


/*
 * Macro to read the interrupt mask registers from both PICs
 */

.macro	readimr

	in	al,SLAVE_IMR
	mov	ah,al
	in	al,MASTER_IMR

.endm




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

	.lcomm	inttab,INTTAB_SIZE	# changed interrupt table
	.lcomm	hwvect,4		# network driver hw int vector
	.lcomm	irqmask,2		# IRQ masks: master(low), slave(high)
#if MASKINT == 1
# if INTMODE == 3
	.lcomm	oldmask,2		# old interrupt mask
# endif
#endif




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

/*
 * The following table specifies all those hardware interrupts, at which a
 * network card cant be installed. It gets modified, and should therefore
 * not be placed into the .rodata segment.
 */

noint_tab:

fdint:	.byte	HWINT_DISKETTE		# diskette driver hardware interrupt
	.byte	HWINT_HD_AT		# hard disk hardware interrupt
math:	.byte	HWINT_MATHERR		# math coprocessor hardware interrupt
mouse:	.byte	HWINT_MOUSE_PS2		# PS/2 mouse hardware interrupt
	.byte	HWINT_KBD		# keyboard hardware interrupt
	.byte	HWINT_CLOCK		# CMOS clock hardware interrupt
	.byte	HWINT_TIMER		# timer hardware interrupt
	.byte	0xFF			# identifies end of table




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

	.global	intsave			# public entry points
	.global	intrestore
	.global	intchanged
	.global	intstart
	.global	intstop
	.global	intset
	.global	intreset
	.global	chkhwint
	.global	dohwint
	.global	endhwint



/*
 **************************************************************************
 *
 * Save interrupt vector table. This is usually called _before_ a network
 * driver gets started.
 * Input:  SS:BP  -  pointer to buffer for interrupt table (has to have a
 *                   size of at least 1kB)
 * Output: none
 * Registers changed: CX, SI, DI, ES
 *
 */
intsave:

	cld
	push	ds
	mov	cx,ss
	mov	es,cx
	xor	si,si
	mov	di,bp
	mov	ds,si
	mov	cx,IDT_NUM
	rep	movsd
	pop	ds
	ret



/*
 **************************************************************************
 *
 * Restore all those interrupts which have been changed by the network
 * driver and identify those which have been changed. This routine usually
 * gets called _after_ a network driver has been started. At the end of
 * this routine, the interrupt table looks exactly like it looked before
 * the network driver started.
 * Input:  SS:BP  -  pointer to buffer of interrupt table
 * Output: Carry flag set if error
 * Registers changed: (E)AX, BX, CX, DX, SI
 *
 */
intrestore:

	push	es
	xor	si,si
	mov	es,si
	mov	bx,offset inttab	# pointer to local interrupt table
	xor	dx,dx			# interrupt vector counter
	xor	cx,cx			# error counter

1:	mov	eax,[bp + si]		# get next old interrupt vector
	xchg	eax,es:[si]		# restore old interrupt vector
	cmp	eax,[bp + si]		# check if vector has been changed
	je	3f
	cmp	bx,offset inttab + INTTAB_SIZE
	jae	2f

	mov	byte ptr inttab_type[bx],1
	mov	byte ptr inttab_vect[bx],dl
	mov	dword ptr inttab_new[bx],eax
	add	bx,INTTAB_LENGTH
	jmp	4f

2:	inc	cx			# increase error counter
3:	xor	eax,eax			# vector has not been changed
4:	mov	[bp + si],eax		# save new vector into buffer
	add	si,4			# go to next vector
	inc	dx
	cmp	dx,IDT_NUM		# continue with next vector
	jb	1b			# when terminating, carry is reset
	jcxz	9f			# check for error
	stc
9:	pop	es
	ret



/*
 **************************************************************************
 *
 * After the network driver got started, the driver interface calls
 * intrestore above to restore the old interrupt table and save all
 * new vectors in the IDT buffer. The driver interface can then identify
 * all those vectors in the IDT buffer it wants to handle directly (for
 * example by calling checkhwint below), and sets these vectors to 0
 * in the IDT buffer. This routine will then check for those vectors
 * which havent been set to 0, and therefore have to remain changed
 * during network driver activation, e.g. which are of type 2. It will
 * save the new vectors into the local array and set type 2.
 * With this routine, the network driver interface also has the chance
 * of modifying a new interrupt vector before it gets set by changing
 * it in the IDT buffer before calling this interrupt table.
 * After executing this routine, the IDT buffer in SS:BP can be released
 * from memory as it is no longer needed.
 * Input:  SS:BP  -  pointer to buffer of interrupt table
 * Output: None
 * Registers changed: (E)AX, BX, CX, DX, SI
 *
 */
intchanged:

	xor	si,si
	xor	dx,dx			# interrupt vector counter
1:	mov	eax,[bp + si]
	or	eax,eax			# check if vector has been changed
	jz	4f

	mov	bx,offset inttab	# pointer to local interrupt table
	mov	cx,INTTAB_NUM
2:	cmp	inttab_vect[bx],dl	# find vector in local interrupt table
	je	3f
	add	bx,INTTAB_LENGTH	# proceed with next interrupt
	loop	2b
	jmp	4f			# interrupt not found - dont restore

3:	mov	inttab_new[bx],eax	# save new interrupt
	mov	byte ptr inttab_type[bx],2
4:	add	si,4			# go to next vector
	inc	dx
	cmp	dx,IDT_NUM		# continue with next vector
	jb	1b			# when terminating, carry is reset
	ret



/*
 **************************************************************************
 *
 * When calling START_UNDI, the processor interrupt table still looks the
 * same as when the bootrom started. This call will set all permanently
 * used interrupts.
 * Input:  None
 * Output: None
 * Registers changed: (E)AX, BX, CX, SI
 *
 */
intstart:

	push	es
	xor	bx,bx
	mov	es,bx
	mov	si,offset inttab
	mov	cx,INTTAB_NUM
1:	cmp	byte ptr inttab_type[si],2
	jne	2f

	mov	eax,inttab_old[si]	# check if vector has already been
	or	eax,eax			# changed
	jnz	2f
	movzx	bx,byte ptr inttab_vect[si]
	shl	bx,2
	mov	eax,inttab_new[si]
	xchg	eax,es:[bx]
	mov	inttab_old[si],eax

2:	add	si,INTTAB_LENGTH
	loop	1b
	pop	es
	ret



/*
 **************************************************************************
 *
 * When STOP_UNDI gets called we have to restore all permanent interrupt
 * vectors.
 * Input:  None
 * Output: Carry flag set if interrupt vectors cant get restored
 * Registers changed: (E)AX, BX, CX, DX, SI
 *
 */
intstop:

	push	es
	xor	dx,dx			# DX is error counter
	mov	es,dx
	mov	si,offset inttab
	mov	cx,INTTAB_NUM
1:	cmp	byte ptr inttab_type[si],2
	jne	4f

	movzx	bx,byte ptr inttab_vect[si]
	shl	bx,2
	mov	eax,es:[bx]		# check if vector has not been changed
	cmp	eax,inttab_new[si]
	je	3f
	inc	dx			# increase error counter
	jmp	4f

3:	xor	eax,eax
	xchg	eax,inttab_old[si]	# set old vector to 0 in local array
	mov	es:[bx],eax		# restore old vector
4:	add	si,INTTAB_LENGTH
	loop	1b
	or	dx,dx			# set carry flag if error
	jz	9f
	stc
9:	pop	es
	ret



/*
 **************************************************************************
 *
 * Before we terminate the network driver, we have to set all interrupt
 * vectors to the state they had after the driver started. This is because
 * the driver might check if it can restore the vector before actually
 * doing it. This routine gets called _before_ a network driver gets
 * terminated.
 * Input:  None
 * Output: Carry flag set if network driver cant get removed from memory
 * Registers changed: (E)AX, BX, CX, DX, SI
 *
 */
intset:

	push	es
	xor	dx,dx			# DX is error counter
	mov	es,dx
	mov	cx,INTTAB_NUM
	mov	si,offset inttab
1:	cmp	byte ptr inttab_type[si],0
	je	6f

	movzx	bx,byte ptr inttab_vect[si]
	shl	bx,2
	mov	eax,inttab_new[si]
	cmp	byte ptr inttab_type[si],2
	jne	3f
	cmp	eax,es:[bx]		# check if vector has been changed
	je	4f
	cmp	dword ptr inttab_old[si],0
	je	3f
	inc	dx
3:	xchg	eax,es:[bx]		# set new vector
4:	mov	inttab_temp[si],eax	# save old vector

6:	add	si,INTTAB_LENGTH	# continue with next table entry
	loop	1b
	or	dx,dx
	jz	9f
	stc
9:	pop	es
	ret



/*
 **************************************************************************
 *
 * Reset all changed interrupts to the state they had before the network
 * driver got terminated. This routine usually gets called _after_ a
 * network driver has been terminated. After this routine has been
 * executed, the network driver cant get restarted any more.
 * Input:  None
 * Output: None
 * Registers changed: (E)AX, BX, CX, SI
 *
 */
intreset:

	push	es
	xor	bx,bx
	mov	es,bx
	mov	cx,INTTAB_NUM
	mov	si,offset inttab
1:	cmp	byte ptr inttab_type[si],0
	je	4f

	movzx	bx,byte ptr inttab_vect[si]	# get vector
	shl	bx,2
	mov	eax,inttab_temp[si]
	cmp	byte ptr inttab_type[si],2	# if the vector has been
	jne	3f				# changed for mode 2 we cant
	cmp	eax,inttab_new[si]		# restore the old vector but
	jne	3f				# have to use the temp vector
	cmp	dword ptr inttab_old[si],0
	je	3f
	mov	byte ptr inttab_type[si],0
	mov	eax,inttab_old[si]
3:	mov	es:[bx],eax			# restore old vector

4:	add	si,INTTAB_LENGTH
	loop	1b
	pop	es
	ret



/*
 **************************************************************************
 *
 * Identify all interrupts redirected by the network driver and find the
 * vector which handles hardware interrupts from the network card. This
 * usually gets called _after_ calling intrestore, because it requires
 * all unchanged interrupt vectors in the buffer to be set to 0.
 * Input:  SS:BP  -  pointer to buffer of interrupt table
 * Output: CX  -  hardware interrupt IRQ number (zero if not found)
 *         DI  -  offset to vector in interrupt table buffer
 * Registers changed: (E)AX, BX, CX, DX, SI, DI
 *
 */
chkhwint:

/*
 * First we have to determine the interrupt vector numbers for certain
 * hardware interrupts. Some hardware interrupt sources are on the mainboard
 * and cant be used by an add-on card. However, some sources are removable
 * or depend upon the type of system used. We need those interrupt numbers
 * to rule them out lateron when checking for the network card hardware
 * interrupt vector.
 */

	int	INT_EQUIP		# get equipment flags
	test	ax,0x0001		# check for installed floppy drives
	jnz	1f
	mov	byte ptr [fdint],0	# dont have a floppy drive
1:	test	ax,0x0002		# check if we have a math coprocessor
	jnz	2f
	mov	byte ptr [math],0	# dont have a math coprocessor
2:	test	ax,0x0004		# check for PS/2 mouse port
	jnz	3f
	mov	byte ptr [mouse],0	# dont have a PS/2 mouse port

/*
 * We can now scan through the IDT buffer to find the network driver hardware
 * interrupt.
 */

3:	cld
	xor	di,di
	xor	cx,cx

0:	cmp	cx,MIN_MASTER_HWINT	# check if current interrupt vector is
	jb	2f			# a hardware interrupt on the master PIC
	cmp	cx,MAX_MASTER_HWINT
	jbe	3f
2:	cmp	cx,MIN_SLAVE_HWINT
	jb	5f			# check if current interrupt vector
	cmp	cx,MAX_SLAVE_HWINT	# is a hardware interrupt on the slave
	ja	5f			# PIC
3:	mov	eax,[bp + di]
	or	eax,eax			# check if interrupt has been changed
	jz	5f

/*
 * A network driver might restore more hardware interrupts than just the one
 * used by the network card. Most commonly this other interrupt vector will be
 * the one for the hardware timer. However, we have to rule out all predefined
 * hardware interrupts, as those cant be used for the network card itself.
 */

	mov	si,offset noint_tab	# check no-interrupt table
6:	lodsb
	cmp	al,cl			# check if current interrupt is a
	je	5f			# reserved vector
	cmp	al,0xFF			# check if at end of table
	jne	6b
	jmp	7f			# we found the network card interrupt

5:	add	di,4
	inc	cx			# continue with next interrupt
	cmp	cx,IDT_NUM
	jb	0b
	jmp	1f

/*
 * If we found a hardware interrupt, save it and set its vector in the IDT
 * buffer to 0. Special handling is required for interrupt 2, which is
 * actually on the slave PIC on AT and higher systems.
 */

7:	cmp	cx,SLAVE_IRQ_NUM	# use interrupt number 9 instead of 2
	jne	2f
	mov	cx,SLAVE_IRQ_REDIR
2:	mov	ax,cx
	sub	cx,MIN_MASTER_HWINT	# compute IRQ number
	jc	3f
	cmp	cx,PIC_IRQ_NUM		# check if IRQ is on master PIC
	jb	4f
3:	mov	cx,ax
	sub	cx,MIN_SLAVE_HWINT
	jc	1f
	cmp	cx,PIC_IRQ_NUM		# check if IRQ is on slave PIC
	jae	1f
	add	cx,PIC_IRQ_NUM		# adjust IRQ number for slave PIC
	jmp	4f

1:	xor	cx,cx			# oops, should never happen
	jmp	9f

4:	mov	ax,1			# compute IRQ mask
	shl	ax,cl
	mov	[irqmask],ax
	xor	eax,eax			# set vector to 0 in IDT buffer
	xchg	eax,[bp + di]		# save hardware interrupt vector
	mov	[hwvect],eax
9:	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Actually process a hardware interrupt. This routine assumes that DS
 * points to our own data segment!
 * Input:  none
 * Output: Carry flag set if this is not our interrupt.
 * Registers changed: AX, BX
 * Interrupt flag NOT changed
 *
 * For an explanation of this code please see the file include/romconfig.i86
 * which also contains the definitions for INTMODE, INTCHECK and MASKINT.
 * Of course this routine has to be reentrant, as it is called as a part of
 * the network card interrupt service routine.
 *
 */
dohwint:

	pushf
	cli
#if INTCHECK == 1
	mov	al,CMD_READ_ISR
	sendcmd				# send ISR read command
	waitpic				# wait for safety
	readdata			# get ISR from both PICs
# if INTMODE == 2
	and	al,_NOT(SLAVE_IRQ_MASK)
	cmp	ax,[irqmask]		# check if we are the only interrupt
	je	1f			# service routine currently running
# else
	test	ax,[irqmask]		# check if our routine has been
	jnz	1f			# called due to a hardware interrupt
# endif
	popf
	stc
	ret
#endif

1:	pushf
	lcall	[hwvect]		# call network driver handler
#if MASKINT == 1
# if INTMODE == 3
	mov	bx,ax			# mask all active interrupts
	and	bl,_NOT(SLAVE_IRQ_MASK)
	readimr				# read interrupt mask
	or	ax,bx			# set bits for interrupts to be masked
	waitpic				# wait for safety
	setimr				# set interrupt mask
	mov	[oldmask],bx		# save old interrupt mask
	waitpic				# wait for safety
	mov	al,CMD_SET_SMM
	sendcmd				# set special mask mode
# else
	readimr				# read interrupt mask
	or	ax,[irqmask]		# set bits for interrupts to be masked
	waitpic				# wait for safety
	setimr				# set interrupt mask
# endif
#endif

9:	popf
	clc				# return without error
	ret



.line __LINE__
/*
 **************************************************************************
 *
 * Terminate a hardware interrupt. This will enable our hardware interrupt
 * (if it has been disabled) and eventually terminate special mask mode.
 * This routine assumes that DS points to our own data segment!
 * Input:  none
 * Output: none
 * Registers changed: AX, BX
 * Interrupt flag NOT changed
 *
 */
endhwint:

#if MASKINT == 1
	pushf
	cli

# if INTMODE == 3
	mov	al,CMD_RST_SMM
	sendcmd				# reset special mask mode
	waitpic				# wait for safety
	readimr				# read interrupt mask
	mov	bx,[oldmask]
# else
	readimr				# read interrupt mask
	mov	bx,[irqmask]
	test	ax,bx
	jz	8f
# endif

	not	bx			# unmask all interrupts previously
	and	ax,bx			# masked
	waitpic				# wait for safety
	setimr				# set interrupt mask
8:	popf
#endif

	ret




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

