home *** CD-ROM | disk | FTP | other *** search
-
- 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
- XA 6: ; else, set carry to show error
- ck_sw_exit:
- ret
- get_parms endp
- cseg e