Metropoli BBS
VIEWER: parchk.asm MODE: TEXT (ASCII)
    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
[ RETURN TO DIRECTORY ]