    page	,132
    title  PARCHK.ASM -- Replacement for IBM-PC ROM BIOS parity error routines
;*****************************************************************************
;*
;* copyright(C) 1984	Skip Gilbrech (CIS 71445,534)
;*			90 Lexington Ave. #10-G
;*			New York, NY 10016
;*			212-685-0551
;*
;* This program may be freely copied/altered for any non-commercial
;* purpose but may not be sold or used in any way as part of any
;* profit-making venture without permission of the author.
;*
;* author = skip gilbrech
;* date written = 02/19/84
;*
;* environment:
;*  system = ibm pc (dos 2.0 - but should work on any version)
;*  processor = microsoft 8086 macro assembler
;*
;* USAGE: PARCHK /R (or) /D
;* /R = Report all errors which occur
;* /D = Disable reporting after an error
;*
;* PARCHK is a resident program which replaces the ibm pc rom-bios NMI
;* (non-maskable interrupt) handler.
;*
;* It will report any memory parity errors to the operator, but will allow
;* the system to continue running.  An installation option lets you choose
;* whether or not to receive continued reports after the first error for
;* a particular channel (system memory or i/o channel).  Regardless of the
;* option chosen, however, the first error for each channel will always be
;* reported.
;*
;* I wrote PARCHK mostly because of the frustration I felt a few days ago
;* when the message 'PARITY CHECK 2' appeared suddenly on my pc's screen.
;* I had seen the message a couple of times before, but never at a time
;* like this:  The machine contained over 3 hours of unsaved work, and I
;* knew there was not a thing I could do about it, since the parity error
;* handler in the rom-bios simply disables interrupts and issues a HLT
;* instruction to the cpu.
;*
;* I have also read a number of messages on the Compuserve IBM-PC SIG
;* which indicate that I'm not the only one to have had this experience.
;*
;* IBM's approach in the rom-bios makes some sense.  The condition of
;* system ram is unknown following a parity error, and continued operation
;* MIGHT result in all sorts of horrible consequences, i.e., hopelessly
;* corrupted data on disk, etc.
;*
;* On my system, though, memory parity errors have been extremely rare,
;* and have probably resulted from a slight glitch on the power line,
;* static, etc.  I really have no idea what would have happened if I
;* had been able to continue operation in those cases.	However, I have
;* crashed the system often enough (most of the time probably executing
;* random data or instructions somewhere) to have some confidence that the
;* most likely, if not the only possible, outcome of a processor gone wild
;* is simply a dead machine, i.e. interrupt vectors wiped out, etc., which
;* must be powered-down and up.
;*
;* So, for me, the risks of continued operation -- at least until important
;* data in ram are saved -- seem relatively small when balanced against
;* the CERTAINTY of lost data when the rom-bios routine gets control:  If
;* data HAS been corrupted, at least there remains the chance of later
;* being able to examine and possibly fix it.
;*
;* Continued operation still DOES represent a risk, however, especially
;* if parity errors occur often, since that probably indicates a serious
;* hardware problem somewhere in the system.
;*
;* If you don't want to take that risk, please don't use this program,
;* as I can't, of course, take responsibility for any damage, real or
;* otherwise, it may cause.
;*
;* On the other hand, if PARCHK is ever responsible for saving a multi-
;* million dollar oil deal which would otherwise have fallen victim to
;* an unruly parity bit......  (suffice it to say that I would deem it
;* an honor to allow you to express your gratitude)
;*
;* The resident part of this code, by the way, uses up a little over 1K
;* of ram.  Most of that space is taken up by routines which save the
;* current screen, write the error message, and then restore the screen.
;* I made no particular effort to make that code as compact as possible,
;* so, if space is at a premium, and/or you like doing such things, please
;* feel free to squeeze and optimize to your heart's content...  As an
;* alternative, if you don't mind junk on your screen, it would be fairly
;* easy to replace most of the error message code with a short routine which
;* uses the rom-bios 'write teletype' function to print a message, but
;* doesn't waste any memory by saving the screen.  It's probably best not
;* to use dos functions here, since a parity error can be reported at any
;* time, even with interrupts disabled and/or while in the middle of a dos
;* routine -- and pc/ms-dos is notoriously non-reentrant.
;*
;**************************************************************************
    page
;**************************************************************************
;*
;*	constants
;*
;**************************************************************************

; general equates:

false		equ	0
true		equ	not false

cr		equ	0dh	; carriage return
lf		equ	0ah	; line feed
bell		equ	7	; ascii bell
spc		equ	' '     ; ascii space, blank
tab		equ	9	; ascii horizontal tab

; program equates:

dosint		equ	21h	; interrupt number for dos functions
prt_str 	equ	9	; dos print string function
get_vers	equ	30h	; dos get version number function
get_int_vec	equ	35h	; dos 2.0 get interrupt vector function
set_int_vec	equ	25h	; dos set interrupt vector function

vidint		equ	10h	; bios video interrupt number
vid_state	equ	15	; bios - get video state
read_curs_pos	equ	3	; bios - read cursor position
set_curs_pos	equ	2	; bios - set cursor position
set_curs_type	equ	1	; bios - set cursor type
write_teletype	equ	14	; bios - write teletype to display
read_ac 	equ	8	; bios - read att/char at curs. position
write_ac	equ	9	; bios - write att/char at curs. position

disp_row	equ	10	; row to display error msg
disp_col	equ	25	; col to display error msg
num_lines	equ	3	; number of text lines to display
normal		equ	7	; normal (white on black) vid. attrib.
reverse_blink	equ	0f0h	; reverse-video blinking vid. attrib.
reverse 	equ	70h	; reverse-video (no blink)

; hardware specific equates:

port_a		equ	60h	; system board 8255 port a address
port_b		equ	61h	; system board 8255 port b address
port_c		equ	62h	; system board 8255 port c address
par_err_mask	equ	0c0h	; mask to test for any parity error
par_ch1_mask	equ	10000000b ; mask to test for system board parity error
par_ch2_mask	equ	01000000b ; mask to test for i/o channel parity error
disa_ch1_mask	equ	00010000b ; mask to disable system board parity checking
enab_ch1_mask	equ	11101111b ; mask to enable system board parity checking
disa_ch2_mask	equ	00100000b ; mask to disable i/o channel parity checking
enab_ch2_mask	equ	11011111b ; mask to enable i/o channel parity checking

nmi_int_no	equ	2	; NMI interrupt number
nmi_port	equ	0a0h	; NMI control port
enable_nmi	equ	80h	; value to output to enable NMI
disable_nmi	equ	0	; value to output to disable NMI

;**************************************************************************

int_vecs	segment at 0
int_vecs	ends

    page
;**************************************************************************

cseg	segment
	assume cs:cseg,ds:cseg

	org	80h			; for processing command line parms.
cmd_ct	label	byte			; number of chars. in command line

	org	100h			; for .COM file
entry:
	jmp	init			; ck for residency, init if not resident

reenable_flag	db	false		; enable continued checking after error?
active_page	db	?		; current active page
curs_pos	dw	?		; current cursor position
orig_nmi_int_vec dd	?		; pointer to orig. NMI service routine

start_row_1	equ	$
par_ch1_msg	db	" PARITY CHECK 1 (System Board) "
length_row	equ	$ - offset start_row_1
par_ch2_msg	db	" PARITY CHECK 2  (I/O channel) "
msg_1		db	"  Parity error reporting for   "
msg_1a		db	" this channel will be disabled "
msg_1b		db	"  this channel will continue   "

scn_storage	dw	(length_row * 5) dup (?) ; storage for current screen
						 ; display

    page
;**************************************************************************
;* This is the start of the new NMI interrupt handler code.
;*
;* 1. Check system board 8255 to determine if a parity error has occurred.
;* 2. If not, pass control to the original NMI vector.
;* 3. If a parity error has occurred:
;*    a. Disable the NMI temporarily.
;*    b. Determine which channel was affected and disable parity reporting.
;*    c. Determine whether error reporting will continue, depending on
;*	 which installation option was selected.
;*    d. Print appropriate message and loop for about 10 seconds.
;*    e. Re-enable reporting on the error channel if the /R option was selected.
;*    f. Re-enable the NMI.

nmi_int_hdlr proc

	pushf		; if we jump to orig. vector, arrive in same condition
	push	ax			;
	in	al,port_c		; read 8255 port
	test	al,par_err_mask 	; has a parity error occurred?
	jnz	nmi_1			; yes, jump to error routine

	pop	ax			; else, restore entering machine state
	popf				;
	jmp	cs:orig_nmi_int_vec	; and pass control to original vector

nmi_1:
	push	ax			; save value read from port c
	mov	al,disable_nmi		; disable NMI
	out	nmi_port,al		;
	pop	ax			; restore port c value

	sti				; allow further interrupts
	push	bx			; save possibly affected registers
	push	cx			;
	push	dx			;
	push	si			;
	push	di			;
	push	bp			;
	push	ds			;
	push	es			;

	push	ax			; save port value again
	mov	ax,cs			; set DS and ES to CS
	mov	ds,ax			;
	mov	es,ax			;

	call	save_screen		; save contents of current display

	pop	ax			; restore port value
	test	al,par_ch2_mask 	; test for i/o channel error
	in	al,port_b		; get value at 8255 port b
	jnz	nmi_2			; jump if i/o channel error

	mov	ah,disa_ch1_mask	; else, put planar disabling mask in AH
	mov	si,offset par_ch1_msg	; put adr of system board msg in SI
	jmp	short nmi_3		; and continue
nmi_2:
	mov	ah,disa_ch2_mask	; put i/o channel disabling mask in AH
	mov	si,offset par_ch2_msg	; put adr of i/o channel msg in SI
nmi_3:
	or	al,ah			; turn on proper bit
	out	port_b,al		; disable appropriate channel to reset
	push	ax			; save mask value in AH

	mov	ax,offset msg_1 	; put adr of 2nd line of message in AX
	mov	bx,offset msg_1a	; put adr of 'remain disabled' msg in BX
	cmp	reenable_flag,false	; is continued checking desired?
	pushf				; save result of comparison
	je	nmi_4			; no, continue
	mov	bx,offset msg_1b	; else, put 'reenable' msg adr in BX
nmi_4:
	call	print_msg		; display our message

; -- loop allow reading the message

	mov	cx,15			; trying to hit 10 seconds...
nmi_5:
	mov	bx,cx			; save outer loop count
	sub	cx,cx			; do each inner loop 65536 times
nmi_6:
	loop	nmi_6			; spin wheels...
nmi_7:
	loop	nmi_7			; ...
	mov	cx,bx			; restore outer loop count
	loop	nmi_5			; and spin some more until done...

	call	restore_screen		; restore original display

	popf				; flags show if reenabling desired
	pop	ax			; restore mask value in AH
	je	nmi_8			; continue if reenabling not wanted

	not	ah			; else, flip bits in AH
	in	al,port_b		; get 8255 port b value
	and	al,ah			; turn masked bit off again
	out	port_b,al		; and reenable the channel
nmi_8:
	pop	es			; restore machine state
	pop	ds			;
	pop	bp			;
	pop	di			;
	pop	si			;
	pop	dx			;
	pop	cx			;
	pop	bx			;
	mov	al,enable_nmi		; reenable NMI
	out	nmi_port,al		;
	pop	ax			;
	popf				;
	iret

nmi_int_hdlr endp

    page
;**************************************************************************
; Save current contents of the portion of the screen we will overwrite.
; Also save the current active page and cursor position.
; Registers not preserved.

save_screen	proc near

	mov	ah,vid_state		; get active page
	int	vidint			; bios video interrupt
	mov	active_page,bh		; store it for others..
	mov	ah,read_curs_pos	; get cursor position and type
	int	vidint			;
	mov	curs_pos,dx		; store data

	mov	dx,(disp_row * 256 + disp_col)	; cursor at start of our msg
	mov	cx,num_lines + 2	; save enough for top & bot. blank
					;  lines, and our message
	mov	di,offset scn_storage	; point DI to buffer
ss_1:
	push	dx			; save cursor position
	push	cx			; save outer loop count
	mov	cx,length_row		; get number of chars. per row
ss_2:
	push	di			; save buffer ptr
	mov	ah,set_curs_pos 	; set cursor pos.
	int	vidint			;
	mov	ah,read_ac		; get attr/char at curs. pos
	int	vidint			;
	pop	di			; restore buffer ptr.
	stosw				; store attr/char in buffer
	inc	dl			; bump DL to next column
	loop	ss_2			; do for the entire row

	pop	cx			; restore outer loop count
	pop	dx			; restore cursor position
	inc	dh			; bump DH to next row
	loop	ss_1			; do for required number of rows
	ret

save_screen	endp

    page
;**************************************************************************
; Restore original contents of the screen.
; Also restore the current cursor position.

restore_screen	proc near

	mov	bh,active_page		; get active page in BH
	mov	dx,(disp_row * 256 + disp_col)	; cursor at start of our msg
	mov	cx,num_lines + 2	; restore amount saved...
	mov	si,offset scn_storage	; point DI to buffer
rs_1:
	push	dx			; save cursor pos.
	push	cx			; save outer loop count
	mov	cx,length_row		; get number of chars. per row
	push	si			; save buffer ptr
rs_2:
	mov	ah,set_curs_pos 	; set cursor pos.
	int	vidint			;
	pop	si			; restore buffer ptr
	lodsw				; get the attr/char in AX, bump SI
	push	si			; save it...
	mov	bl,ah			; put attr. where bios wants it
	mov	ah,write_ac		; write attr/char at curs. pos
	push	cx			; save row chars remaining
	mov	cx,1			; tell bios to write 1 char.
	int	vidint			;
	pop	cx			; restore row chars remaining
	inc	dl			; bump DL to next column
	loop	rs_2			; do for the entire row

	pop	si			; keep stack in order....
	pop	cx			; restore outer loop count
	pop	dx
	inc	dh			; bump DH to next row
	loop	rs_1			; do for required number of rows

	mov	dx,curs_pos		; restore original cursor pos
	mov	ah,set_curs_pos 	;
	int	vidint			;
	ret

restore_screen	endp

    page
;**************************************************************************
; Print message to console
; 1st and last line blanks - start at current curs. pos.
; SI has adr. of 1st string, AX has second, and BX has third.

print_msg	proc near

	push	bx		; push string addresses
	push	ax		;
	push	si		;

	mov	al,bell 	; first put out a beep
	mov	ah,write_teletype ;
	int	vidint		;

	mov	bl,reverse_blink ; reverse-video blinking vid. attrib.
	mov	bh,active_page	; get active page in BH
	mov	cx,length_row	; length of a row
	mov	dx,(disp_row * 256 + disp_col)	; cursor at start of our msg

	call	blank_line	; put out the first blank line
	pop	si		; address of 1st line
	call	char_line	;
	pop	si		; ...2nd line
	mov	bl,reverse	; don't blink for the rest of msg
	call	char_line	;
	pop	si		; ...3rd line
	call	char_line	;
	call	blank_line	; ... and last blank line
	mov	dx,curs_pos	; restore original cursor pos
	mov	ah,set_curs_pos ;
	int	vidint		;
	ret

blank_line:			; write CX blanks, bump row position
	mov	ah,set_curs_pos ; set cursor pos.
	int	vidint		;
	mov	ah,write_ac	; write attr/char at curs. pos
	mov	al,' '          ; write blanks
	int	vidint		;
	inc	dh		; bump row position
	ret

char_line:

; -- attrib. in BL, active page in BH, count in CX, starting pos. in DX
;    get chars. starting at DS:SI

	push	cx			; save count of chars. in line
	push	dx			; save starting curs. pos.
	push	si			; save buffer ptr
cl_1:
	mov	ah,set_curs_pos 	; set cursor pos.
	int	vidint			;
	pop	si			; restore buffer ptr
	lodsb				; get char. from message string in AL
	push	si			; save bumped string ptr.
	push	cx			; save chars. remaining in line
	mov	cx,1			; so bios will write one char.
	mov	ah,write_ac		; write attr/char at curs. pos
	int	vidint			;
	pop	cx			; restore chars. remaining in line
	inc	dl			; bump DL to next column
	loop	cl_1			; do for the entire row

	pop	si			; keep stack in order....
	pop	dx			; restore starting curs. pos.
	inc	dh			; bump DH to next row
	pop	cx			; restore count of chars. in line
	ret

print_msg	endp

; -- This is the end of the code which will be made permanently resident.

end_nmi_int_hdlr	equ	$
len_nmi_int_hdlr	equ	(end_nmi_int_hdlr - nmi_int_hdlr)

    page
;**************************************************************************

install_msg	db cr,lf,"PARCHK (v 1.0) installed -- Copyright (C) 1984 Skip Gilbrech",cr,lf,'$'
resident_msg	db cr,lf,bell,"PARCHK (v 1.0) already resident",cr,lf,'$'

usage_msg	db cr,lf

db "PARCHK (v 1.0) -- Copyright (C) 1984 Skip Gilbrech",cr,lf,cr,lf

db "USAGE: PARCHK /R (or) /D",cr,lf
db "/R = Report all errors which occur",cr,lf
db "/D = Disable reporting after an error",cr,lf,cr,lf

db "PARCHK replaces the ibm pc rom-bios NMI (non-maskable interrupt) handler.",cr,lf
db "It will report any memory parity errors to the operator, but will allow",cr,lf
db "the system to continue running.  An installation option lets you choose",cr,lf
db "whether or not to receive continued reports after the first error for",cr,lf
db "a particular channel (system memory or i/o channel).  Regardless of the",cr,lf
db "option chosen, however, the first error for each channel will always be",cr,lf
db "reported.  The source file, PARCHK.ASM, contains some cautions which",cr,lf
db "you should read before using this program.",cr,lf,cr,lf,'$'

    page
;**************************************************************************
;* Initialization procedure:
;*
;*	1. Check whether the routine is already resident by getting the current
;*	   interrupt vector and checking whether our code is servicing it.
;*	   If so, just print message and exit without installing
;*	2. Check command line to see if continued checking is wanted after
;*	   error and set flag accordingly.  If no parms or unknown parms,
;*	   print informational message and exit without installing.
;*	3. Otherwise, change vector to point to our handler, print installation
;*	   message, and execute dos 'terminate but stay resident' interrupt,
;*	   installing only the code which will actually be used.
;*
;* The idea, and much of the code, for the 'testresident' routine, came
;* from Tony A. Rhea's KEYSTAT program.  His copyright notice is reproduced
;* below:
;*
;*	KEYSTAT -- Copyright (C) 1983 Tony A. Rhea
;*	This program may be copied for non-commercial use.
;*	Adapted from KEYFLAGS (PC-World, Oct. 83, page 266) by Morton Kaplon
;*

init	proc	near

	call	testresident		; see if handler already resident,
	jz	init_1			; ZF set = already resident -- print msg and exit

	call	get_parms		; see if reenabling NMI desired, set flag
	jc	init_2			; if parm error, print usage message
					; else, install the handler
	mov	al,nmi_int_no		; interrupt number in AL
	mov	ah,set_int_vec		; funct. number for set int. vector
	mov	dx,offset nmi_int_hdlr	; offset of resident code
	int	dosint			; set the vector

	mov	dx,offset install_msg	; print install message
	mov	ah,prt_str		;
	int	dosint			;
	mov	dx,offset end_nmi_int_hdlr ; set DX to end of resident code
	int	27h			; dos terminate but stay resident func.

init_1:
	mov	dx,offset resident_msg	; msg showing already installed
	jmp	short init_3		; print it and exit
init_2:
	mov	dx,offset usage_msg	; general info. msg.
init_3:
	mov	ah,prt_str		;
	int	dosint			;
	int	20h			; return to dos (nothing made resident)

init	endp

    page
;**************************************************************************
;* see if our handler is already resident -- zero flag set if it is.
;*
;* AX, BX, CX, SI, DI changed, ES preserved

testresident proc near

	push	es			;
	mov	al,nmi_int_no		; interrupt number in AL
	call	get_vec 		; interrupt vector returned in ES:BX
	mov	orig_nmi_int_vec,bx	; store IP of current servicer
	mov	orig_nmi_int_vec[2],es	; store CS of current servicer
	mov	cx,len_nmi_int_hdlr	; number of bytes to match
	mov	si,offset nmi_int_hdlr	; point DS:SI to our code
	mov	di,bx			; make ES:DI point to current handler
	cld				; auto increment
	repe	cmpsb			; compare while equal - zero flag set on exit
					; if all bytes matched (already resident)
	pop	es			;
	ret

testresident endp

    page
;**************************************************************************
;* Get interrupt vector for interrupt number in AL.  On exit, ES:BX contain
;* CS:IP for vector.
;*
;* Only ES and BX changed

get_vec proc near

	push	ax			; save entering value
	push	ax			; save interrupt number requested
	mov	ah,get_vers		; dos get version function
	int	dosint			; major version returned in AL, minor in AH
	pop	bx			; restore int. number requested
	cmp	al,2			; less than 2 = pre dos 2.0
	jb	gv_direct		; get vector directly

	mov	al,bl			; move requested interrupt vector into AL
	mov	ah,get_int_vec		; dos get int. vector function
	int	dosint			; CS:IP returned in ES:BX
	pop	ax			;
	ret

gv_direct:				; get vector directly
					; first, convert number in BL to offset in BX
	xor	bh,bh			; make sure BH = 0
	shl	bx,1			; multiply by 4
	shl	bx,1			;
	mov	ax,int_vecs		; point ES to segment for int. vectors
	mov	es,ax			;
	mov	ax,es:[bx+2]		; get CS value into AX
	mov	bx,es:[bx]		; get IP value into BX
	mov	es,ax			; mov CS value into ES
	pop	ax			;
	ret

get_vec endp

    page
;**************************************************************************
;* Scan command line for /R or /D installation option -- carry set
;* on exit if no valid parm.
;*
;* AX,BX,SI changed

get_parms	proc near

	mov	bx,offset cmd_ct	; BX pts to number of cmd line chars
	mov	si,[bx] 		; get count
	inc	bx			; point BX to 1st char
	and	si,0ffh 		; max. 127 chars. - set flags
	jz	gp_err_exit		; no parms, skip the rest
	mov	byte ptr [bx][si],0	; make command line null terminated

gp_top_loop:
	mov	al,[bx] 		; get the char
	or	al,al			; null?
	je	gp_err_exit		; if so, return error
	cmp	al,spc			; space?
	je	gp_bot_loop		; yes, get another char
	cmp	al,tab			; tab?
	je	gp_bot_loop		; yes, get another char
	cmp	al,'/'                  ; see if possible /R or /D switch
	jne	gp_err_exit		; no, return error
	call	ck_switches		; check parm, carry set on error
	jc	gp_err_exit		; carry set, return error
	jmp	gp_exit 		;

gp_bot_loop:
	inc	bx			; point to next char
	jmp	gp_top_loop		; go get it

gp_err_exit:
	stc				; set carry to show error
gp_exit:				; if got here by jump, carry is reset
	ret

ck_switches:				; ck parms ( BX pts to '/' )
	inc	bx			; point to possible switch
	mov	al,[bx] 		; get char
	cmp	al,'Z'                  ; check if possibly upper-case
	jbe	ck_sw1			; no? continue
	sub	al, 'a' - 'A'           ; make upper-case
ck_sw1:
	cmp	al,'R'                  ; reenable checking after parity error?
	jne	ck_sw2			; no? continue
	mov	reenable_flag,true	; else, set flag
	ret				; and return
ck_sw2:
	cmp	al,'D'                  ; disable checking after error?
	je	ck_sw_exit		; yes, return ok
	stc				; else, set carry to show error

ck_sw_exit:
	ret

get_parms	endp

cseg	ends

end	entry
HED!  6          