/*
 **************************************************************************
 *
 * PKTWATCH - Log packets going through a packet driver
 *
 * Module:  pktwatch.S86
 * Purpose: Log packets going through a packet driver
 * Entries: _start
 *
 **************************************************************************
 *
 * Copyright (C) 1995-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: pktwatch.S86,v 1.9 2007/01/06 18:30:49 gkminix Exp $
 *
 **************************************************************************
 *
 */

#include <common.i86>
#include <system/bios.i86>
#include <dos/general.i86>
#include <dos/functions.i86>
#include <dos/exec.i86>

.file __FILE__
.line __LINE__




/*
 **************************************************************************
 *
 * The purpose of this program is to log all packets onto the screen, which
 * go through a packet driver. The packet driver interrupt has to be specified
 * on the command line in hex. It then splits the screen into two halves by
 * using the 43x80 text resolution on a EGA/VGA card. The upper 25 lines
 * receive the unmodified output from the original screen, and the lower
 * lines receive the packets in hex dump.
 *
 * The usage is:
 *
 *	pktwatch [-d] [hw int number]
 *
 * If a hardware interrupt number is specified as a decimal number, pktwatch
 * will print a star character everytime the hardware interrupt gets activated.
 * If the -d command line option is not given, pktwatch redirects the hardware
 * interrupt immediately following the start of the program. When -d is speci-
 * fied, redirecting the hardware interrupt occurs when a packet driver handle
 * is first assigned. This makes a difference when used together with the
 * bootrom kernel: if the hardware interrupt redirection has been delayed, the
 * star will be printed everytime the interrupt signal appears at the processor
 * INT pin, while without the -d option the star gets printed when the interrupt
 * execution reaches the packet driver.
 *
 */


/*
 **************************************************************************
 *
 * Command line flags:
 */
#define FLAG_DELAY	0x00000004	/* Delay flag: character 'd' */
#define FLAG_INT3	0x00000100	/* Interrupt 3 flag: character 'i' */

#define ALL_FLAGS	0x00000104	/* all allowed flags */



/*
 **************************************************************************
 *
 * Miscellaneous defines:
 *
 */
#define	VIDEO_SEG	0xB800		/* segment of video buffer */

#define	MAX_BYTES	36		/* max num of buffer bytes to display */
#define	ADDR_SIZE	6		/* size of hardware address */
#define	DEST_OFS	6		/* offset to dest addr in MAC header */
#define	TYPE_OFS	12		/* offset to packet type in MAC header */
#define	DATA_OFS	14		/* offset to data area in ethernet pkt */




.line __LINE__
/*
 **************************************************************************
 *
 * There is no other than a code segment. All data will be placed in it
 * as well. The resident section of the program starts here.
 *
 */
	.text


	.global	_start			# define startup point

_start:	jmp	start



/*
 **************************************************************************
 *
 * Data segment:
 *
 */

scnlines:	.word	0		# number of screen lines
scncolumns:	.word	0		# number of screen columns
scnbytes:	.word	0		# number of bytes per screen line
vidbufsize:	.word	0		# video buffer size
vidoffset:	.word	0		# offset into second half of screen
curoffset:	.word	0		# current character offset
lineoffset:	.word	0		# offset to horizontal screen line

flags:		.long	0		# command line flags

pktint:		.word	0		# packet driver interrupt number
oldpkt_vect:	.long	0		# old packet driver interrupt vector
oldreceiver:	.long	0		# pointer to old receiver routine

hwint:		.word	0		# hardware interrupt number
oldhw_vect:	.long	0		# old hardware interrupt vector
oldint3_vect:	.long	0		# old interrupt 3 vector



/*
 **************************************************************************
 *
 * Routine to terminate pktwatch services. This will wait for a key press,
 * and then restore the screen.
 * Input:  none
 * Output: none
 * Registers used: AX
 *
 */
terminate:

	push	si
	push	ds
	push	eax
	xor	ax,ax			# first restore all interrupt vectors
	mov	ds,ax
	mov	si,cs:[pktint]
	shl	si,2
	mov	eax,cs:[oldpkt_vect]
	mov	[si],eax
	test	dword ptr cs:[flags],FLAG_INT3
	jz	3f
	mov	eax,cs:[oldint3_vect]
	mov	ds:[3 * 4],eax
3:	mov	si,cs:[hwint]		# check if we redirected the
	or	si,si			# hardware interrupt
	jz	1f
	test	dword ptr cs:[flags],FLAG_DELAY
	jz	2f
	mov	eax,cs:[oldhw_vect]
	or	eax,eax
	jz	1f
2:	shl	si,2
	mov	[si],eax		# restore hardware interrupt
1:	pop	eax

	mov	ax,cs			# let DS point to data segment
	mov	ds,ax
	call	scrollup		# scroll screen up one line
	mov	si,offset waitms
	call	printstr		# print waiting message
	xor	ah,ah
	int	INT_KBD			# wait for key press
	mov	ax,0x0003		# restore screen to mode 3
	int	INT_VIDEO
	pop	ds
	pop	si
	ret

waitms:	.asciz	"Terminating pktwatch. Press a key to continue..."



/*
 **************************************************************************
 *
 * New packet driver interrupt.
 * Input:  none
 * Output: none
 * Registers changed: none
 *
 */
new_pkt:

	jmp	1f

	.byte	0
pktsig:	.ascii	"PKT DRVR"		# packet driver signature

1:	push	ax
	cmp	ah,2			# initialize access type?
	je	4f
	cmp	ah,3			# release access type?
	je	3f
	cmp	ah,4			# send packet?
	je	2f
	cmp	ah,5			# terminate packet driver
	jne	9f

/*
 * Requested to terminate the packet driver services.
 */

	mov	al,0x54			# character indicating 'terminate'
	call	printline
	call	terminate		# terminate pktwatch services and
	jmp	9f			# then let the packet driver do the same

/*
 * Requested to send a packet.
 */

2:	mov	al,0x53			# character indicating 'send'
	call	printline
	call	printbuf
	jmp	9f

/*
 * Requested to release an access type.
 */

3:	mov	al,0x44			# character indicating 'delete'
	call	printline
	mov	dword ptr cs:[oldreceiver],0

	test	dword ptr cs:[flags],FLAG_DELAY
	jz	5f
	mov	ax,cs:[hwint]		# check if we redirected the
	or	ax,ax			# hardware interrupt
	jz	5f
	mov	ax,cs:[oldhw_vect + 0]
	or	ax,cs:[oldhw_vect + 2]
	jz	5f

	push	ds
	push	si
	xor	ax,ax
	mov	ds,ax
	mov	si,cs:[hwint]
	shl	si,2
	cli
	mov	ax,word ptr cs:[oldhw_vect + 0]
	mov	word ptr [si + 0],ax
	mov	ax,word ptr cs:[oldhw_vect + 2]
	mov	word ptr [si + 2],ax
	mov	dword ptr cs:[oldhw_vect],0
	sti
	pop	si
	pop	ds
5:	jmp	9f

/*
 * Requested to set new packet access type. The receiver routine is replaced
 * by a new one which prints every received packet.
 */

4:	mov	al,0x4E			# character indicating 'new'
	call	printline
	mov	ax,word ptr cs:[oldreceiver + 0]
	or	ax,word ptr cs:[oldreceiver + 2]
	jnz	9f
	mov	word ptr cs:[oldreceiver + 0],di
	mov	word ptr cs:[oldreceiver + 2],es
	mov	ax,cs
	mov	es,ax			# replace old receiver routine with
	mov	di,offset new_receiver	# new one

	test	dword ptr cs:[flags],FLAG_DELAY
	jz	9f
	mov	ax,cs:[hwint]		# check if we have to redirect the
	or	ax,ax			# hardware interrupt
	jz	9f
	mov	ax,cs:[oldhw_vect + 0]	# check if we already redirected the
	or	ax,cs:[oldhw_vect + 2]	# hardware interrupt
	jnz	9f

	push	ds
	push	si
	xor	ax,ax
	mov	ds,ax
	mov	si,cs:[hwint]		# determine address of hardware
	shl	si,2			# interrupt vector
	cli
	mov	ax,[si + 0]		# save old hardware interrupt vector
	mov	word ptr cs:[oldhw_vect + 0],ax
	mov	ax,[si + 2]
	mov	word ptr cs:[oldhw_vect + 2],ax
	mov	word ptr [si + 0],offset new_hwint
	mov	word ptr [si + 2],cs
	sti
	pop	si
	pop	ds
9:	pop	ax
	ljmp	cs:[oldpkt_vect]	# call old packet vector



/*
 **************************************************************************
 *
 * New hardware interrupt handler
 * Input:  none
 * Output: none
 * Registers changed: none
 *
 */
new_hwint:

	push	ax
	mov	al,0x2A			# print a star character for each
	call	printline		# hardware interrupt
	pop	ax
	ljmp	cs:[oldhw_vect]		# call old hardware interrupt vector



/*
 **************************************************************************
 *
 * New debug interrupt handler
 * Input:  none
 * Output: none
 * Registers changed: none
 *
 */
new_int3:

	push	ax
	mov	al,0x23			# print a pound character for each
	call	printline		# debug interrupt
	pop	ax
	iret



/*
 **************************************************************************
 *
 * New receiver routine. This routine gets called by the packet driver
 * whenever a new packet arrives.
 * Input:  BX     -  handle
 *         AX     -  flag indicating first or second call
 *         CX     -  buffer length
 *         DS:SI  -  pointer to receive buffer (only if AX = 1)
 * Output: none
 * Registers changed: none
 *
 */
new_receiver:

	push	ax
	or	ax,ax			# check for first or second call
	jnz	1f
	mov	al,0x43			# character indicating 'check'
	call	printline
	jmp	9f

1:	mov	al,0x52			# character indicating 'receive'
	call	printline
	call	printbuf		# print buffer contents
9:	pop	ax
	ljmp	cs:[oldreceiver]



/*
 **************************************************************************
 *
 * Print a packet buffer into the last screen line
 * Input:  DS:SI  -  pointer to buffer
 *         CX     -  size of buffer
 *         AL     -  character in first column
 * Output: none
 * Registers changed: AX
 *
 */
printbuf:

	push	es
	push	ds
	pop	es			# point ES:SI to buffer
	push	cs
	pop	ds
	push	bx

	xor	bx,bx
	cmp	al,0x53
	jne	6f			# select source or destination address
	mov	bx,DEST_OFS		# to print
6:	call	scrollup		# scroll screen up one line
	call	printchr
	mov	al,0x20			# first print the character indicating
	call	printchr		# direction of packet

1:	mov	al,es:[bx + si]
	call	printbyte		# print next byte
	cmp	bx,DEST_OFS - 1
	jne	4f
	add	bx,ADDR_SIZE		# skip destination addr if source addr
4:	cmp	bx,TYPE_OFS - 1		# printed
	jne	5f
	add	bx,DATA_OFS - TYPE_OFS	# skip packet type
5:	cmp	bx,DATA_OFS - 1
	jb	3f
	mov	al,0x20			# print blanks between data bytes
	call	printchr
3:	inc	bx
	cmp	bx,cx			# check if at the end of the buffer
	jae	9f
	cmp	bx,MAX_BYTES		# cant write more than one line
	jb	1b

9:	pop	bx
	push	es
	pop	ds			# restore DS:SI
	pop	es
	ret



/*
 **************************************************************************
 *
 * Print a byte in hex form onto screen
 * Input:  AL  -  byte value
 * Output: none
 * Registers changed: AX
 *
 */
printbyte:

	push	ax
	shr	al,4			# first print upper nibble
	call	printnib
	pop	ax

printnib:

	and	al,0x0F
	add	al,0x30
	cmp	al,0x39			# convert nibble and print it
	jbe	1f
	add	al,7
1:	call	printchr
	ret



/*
 **************************************************************************
 *
 * Scroll the lower half of the screen up one line.
 * Input:  none
 * Output: none
 * Registers used: none
 *
 */
scrollup:

	cld
	push	es
	push	di
	push	ds
	push	si
	push	cx
	mov	cx,VIDEO_SEG
	mov	es,cx			# set video buffer segment
	mov	ds,cx
	mov	si,cs:[scnbytes]
	mov	di,cs:[vidoffset]	# compute start of source and
	add	si,di			# destination area
	mov	cx,cs:[vidbufsize]	# compute number of words to move
	sub	cx,si
	shr	cx,1
	rep	movsw			# now scroll the screen
	mov	si,ax			# save AX
	mov	ax,0x0720		# fill last line with blanks
	mov	cx,cs:[scncolumns]
	rep	stosw
	mov	ax,si			# restore AX
	pop	cx
	pop	si
	pop	ds
	sub	di,[scnbytes]
	mov	[curoffset],di		# set current character position to
	pop	di			# first column in last line
	pop	es
	ret



/*
 **************************************************************************
 *
 * Print a string into the last screen line.
 * Input:  DS:SI  -  pointer to zero terminated string
 * Output: none
 * Registers used: AX, SI
 *
 */
printstr:

	cld
	push	es
	push	di
	mov	di,VIDEO_SEG		# set video buffer segment
	mov	es,di
	mov	di,[curoffset]
	mov	ah,0x07			# white on black
1:	lodsb				# now print the string
	or	al,al			# check for end of string
	jz	2f
	stosw				# store character into video buffer
	jmp	1b
2:	pop	di
	pop	es
	ret



/*
 **************************************************************************
 *
 * Print a character into the last screen line.
 * Input:  AL  -  character to print
 * Output: none
 * Registers used: AX
 *
 */
printchr:

	cld
	push	es
	push	di
	mov	di,VIDEO_SEG		# set video buffer segment
	mov	es,di
	mov	di,[curoffset]
	mov	ah,0x07			# white on black
	stosw				# write character onto screen
	mov	[curoffset],di		# save new character position
	pop	di
	pop	es
	ret



/*
 **************************************************************************
 *
 * Print a character into the horizontal screen line
 * Input:  AL  -  character to print
 * Output: none
 * Registers used: AH
 *
 */
printline:

	cld
	push	es
	push	di
	mov	di,VIDEO_SEG		# set video buffer segment
	mov	es,di
	mov	di,cs:[lineoffset]
	mov	ah,0x70			# black on white
	stosw				# write character onto screen
	cmp	di,cs:[vidoffset]
	jb	1f
	sub	di,cs:[scncolumns]	# compute beginning of horizontal
	sub	di,cs:[scncolumns]	# line
1:	mov	cs:[lineoffset],di	# save next write position
	mov	word ptr es:[di],0x70C4	# print horizontal character again
	pop	di
	pop	es
	ret




/*
 **************************************************************************
 *
 * Start the non-resident section here:
 *
 */
start:	push	cs
	pop	ds

/*
 * Print copyright information
 */

	mov	dx,offset cpymsg		# print copyright
	mov	ah,DOS_FN_WRITE_STRING
	int	INT_DOS

/*
 * Determine the flag switches and hardware interrupt number from the
 * command line
 */

	cld
	mov	si,_I(psp_cmdlsize)
	lodsb				# get length of command line
	mov	cl,al
	xor	ch,ch
	xor	bx,bx			# put result into BX

1:	lodsb
	cmp	al,0x0D			# check if end of line
	je	7f
	cmp	al,0x20			# skip any leading spaces
	jne	5f
	loop	1b			# continue with next character
	jmp	7f			# command line is empty

5:	cmp	al,0x2D			# check for minus character
	jne	6f
	lodsb
	cmp	al,0x41			# check if in range 'A'..'Z'
	jb	9f
	cmp	al,0x5A
	jbe	4f
	cmp	al,0x61			# check if in range 'a'..'z'
	jb	9f
	cmp	al,0x7A
	ja	9f
	sub	al,0x20			# convert into upper case letter
4:	mov	cl,al
	sub	cl,0x41			# convert into bit position
	mov	eax,0x00000001
	shl	eax,cl			# set bit in flags word
	test	eax,_NOT(ALL_FLAGS)
	jnz	9f			# check if flag is allowed
	or	[flags],eax
	lodsb
	cmp	al,0x0D			# check if flag is followed by CR
	je	7f			# or space character
	cmp	al,0x20
	je	1b
	jmp	9f

6:	cmp	al,0x0D			# check if at end of line
	je	8f
	cmp	al,0x30			# check if character is a valid number
	jb	9f
	cmp	al,0x39
	ja	9f
	sub	al,0x30			# convert character into single digit
	xor	ah,ah
	push	ax
	mov	ax,10			# multiply old number by 10
	mul	bx
	pop	bx
	or	dx,dx			# check if number exceeds max range
	jnz	9f
	add	bx,ax			# add new digit
	cmp	bx,16			# check if maximum value exceeded
	jae	9f
	lodsb
	loop	6b			# continue with next character
	jmp	8f			# end of string reached

9:	mov	dx,offset useerr	# print error message
	jmp	3f

8:	mov	ax,0x08			# get base hardware interrupt number
	cmp	bx,8			# check if on second PIC
	jb	1f
	mov	ax,0x70			# get base for second half
	sub	bx,8
1:	add	ax,bx			# compute interrupt number
	mov	[hwint],ax		# save it

/*
 * Find a packet driver.
 */

7:	mov	bx,0x0020
2:	mov	[pktint],bx		# save packet driver interrupt number
	mov	al,bl
	mov	ah,DOS_FN_GET_INTERRUPT	# determine packet driver vector
	int	INT_DOS
	call	checkpkt		# check if there really is a packet drvr
	jnc	4f
	mov	bx,[pktint]
	inc	bx			# continue with next possible interrupt
	cmp	bx,0x007F
	jbe	2b
	mov	dx,offset pkterr	# print error message
3:	mov	ah,DOS_FN_WRITE_STRING
	int	INT_DOS
	mov	ax,DOS_FN_WORD(DOS_FN_EXIT) + 0x01
	int	INT_DOS			# terminate program
5:	jmp	5b

4:	mov	word ptr [oldpkt_vect + 0],bx
	mov	word ptr [oldpkt_vect + 2],es

/*
 * Initialize the screen.
 */

	call	setvideo			# setup new video mode
	jnc	8f
	mov	dx,offset viderr		# print error message
	jmp	3b

/*
 * From this point on we are going to manipulate interrupt vectors, so
 * there is no way back now.
 */

8:	mov	dx,offset new_pkt
	mov	ax,[pktint]
	mov	ah,DOS_FN_SET_INTERRUPT
	int	INT_DOS			# set new packet driver interrupt

	test	dword ptr [flags],FLAG_INT3
	jz	1f
	mov	ax,DOS_FN_WORD(DOS_FN_GET_INTERRUPT) + 3
	int	INT_DOS
	mov	word ptr [oldint3_vect + 0],bx
	mov	word ptr [oldint3_vect + 2],es
	mov	dx,offset new_int3
	mov	ax,DOS_FN_WORD(DOS_FN_SET_INTERRUPT) + 3
	int	INT_DOS			# set new debug interrupt

1:	test	dword ptr [flags],FLAG_DELAY
	jnz	9f
	mov	ax,[hwint]		# check if we have to redirect the
	or	ax,ax			# hardware interrupt
	jz	9f
	mov	ah,DOS_FN_GET_INTERRUPT	# determine hardware interrupt vector
	int	INT_DOS
	mov	word ptr [oldhw_vect + 0],bx
	mov	word ptr [oldhw_vect + 2],es
	mov	dx,offset new_hwint
	mov	ax,[hwint]
	mov	ah,DOS_FN_SET_INTERRUPT
	int	INT_DOS			# set new hardware interrupt

9:	mov	dx,offset start		# determine number of resident
	shr	dx,4			# paragraphs
	inc	dx
	mov	ax,DOS_FN_WORD(DOS_FN_TERM_STAY)
	int	INT_DOS			# terminate and stay resident
	lret


cpymsg:	.byte	0x0D,0x0A
	.ascii	"pktwatch - Packet driver logging utility"
	.byte	0x0D,0x0A
	.ascii	"Copyright (C) 1996-2007 Gero Kuhlmann"
	.byte	0x0D,0x0A,0x0D,0x0A
	.ascii	"$"

useerr:	.ascii	"usage: pktwatch [-d] [hw int number]"
	.byte	0x0D,0x0A
	.ascii	"$"
pkterr:	.ascii	"No packet driver found"
	.byte	0x0D,0x0A
	.ascii	"$"
viderr:	.ascii	"Cannot switch video mode"
	.byte	0x0D,0x0A
	.ascii	"$"



/*
 **************************************************************************
 *
 * Check if there really is a packet driver at the given interrupt.
 * Input:  ES:BX  -  packet driver interrupt vector to check
 * Output: Carry set, if no packet driver
 * Registers changed: AX, CX, SI, DI
 *
 */
checkpkt:

	mov	ax,es			# check if the interrupt vector
	or	ax,bx			# points to somewhere
	jz	8f
	mov	di,bx
	add	di,3			# DI points to start of signature
	mov	si,offset pktsig
	mov	cx,8
	repe	cmpsb			# check for packet driver
	clc
	jz	9f			# found packet driver
8:	stc
9:	ret



/*
 **************************************************************************
 *
 * Setup video mode to 43x80 (or 50x80 on VGA). This is done by using the
 * 8x8 character set.
 * Input:  none
 * Output: Carry flag set if error
 * Registers changed: AX, BX, CX, DX, DI
 *
 */
setvideo:

	mov	ah,0x0F
	int	INT_VIDEO		# get current video mode
	cmp	al,7			# monochrome not supported
	je	8f
	cmp	al,3			# if its mode 3 we dont have
	je	1f			# to change the mode
	mov	ax,0x0003		# set video mode to 3
	int	INT_VIDEO

1:	mov	ah,0x12			# determine current EGA configuration
	mov	bx,0x0110		# this is used to check if we really
	int	INT_VIDEO		# have an EGA or VGA card
	or	bh,bh			# BH has to be set to 0 by BIOS
	jnz	8f

	push	es
	mov	ax,BIOS_SEG
	mov	es,ax
	mov	al,es:[BIOS_LINES]
	xor	ah,ah
	mov	[scnlines],ax		# save current screen size
	mov	bx,es:[BIOS_VIDSIZE]
	mov	[vidbufsize],bx
	mov	bx,es:[BIOS_COLUMNS]
	mov	[scncolumns],bx
	inc	ax
	shl	ax,1
	mul	bx			# compute address of first character
	or	dx,dx			# after first half of screen
	jz	2f			# it should not be larger than 64kB
	pop	es
8:	stc				# return with error
	jmp	9f

2:	mov	[lineoffset],ax		# save offset to horizontal line
	mov	ax,0x1112		# load 8x8 character set into
	xor	bl,bl			# block no. 0 and reset screen
	int	INT_VIDEO
	mov	al,es:[BIOS_LINES]
	xor	ah,ah			# restore original screen size
	xchg	[scnlines],ax		# this will let the BIOS output
	mov	es:[BIOS_LINES],al	# routines just access the upper 25
	inc	word ptr [scnlines]	# scnlines is number of lines - 1
	mov	ax,es:[BIOS_VIDSIZE]
	xchg	[vidbufsize],ax
	mov	es:[BIOS_VIDSIZE],ax
	mov	ax,es:[BIOS_COLUMNS]
	mov	cx,ax			# save number of columns for later
	xchg	[scncolumns],ax
	mov	es:[BIOS_COLUMNS],ax
	pop	es

	shl	cx,1			# compute number of bytes per line
	mov	[scnbytes],cx
	mov	ax,[scnlines]
	mul	cx			# compute real size of video buffer
	or	dx,dx
	jz	3f
	mov	ax,0xFFFF		# if screen buffer too large, limit it
3:	mov	[vidbufsize],ax		# to 64kB

	cld
	push	es
	shr	cx,1
	mov	ax,VIDEO_SEG
	mov	es,ax
	mov	di,[lineoffset]
	mov	ax,0x70C4		# print horizontal line
	rep	stosw
	pop	es
	mov	[vidoffset],di		# save offset to second half
	clc				# return without error
9:	ret




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

	.end

