The MS-DOS Encyclopedia: Figures

Archive of outdated Microsoft articles and reference materials


The MS-DOS Encyclopedia

Figures

Figure 2-11.
A simple program to run an application as an MS-DOS shell.


; SHELL.ASM   A simple program to run an application as an
;             MS-DOS shell program. The program name and
;             startup parameters must be adjusted before
;             SHELL is assembled.
;
; Written by William Wong
;
; To create SHELL.COM:
;
;             C>MASM SHELL;
;             C>LINK SHELL;
;             C>EXE2BIN SHELL.EXE SHELL.COM

stderr  equ 2                ; standard error
cr      equ 0dh              ; ASCII carriage return
lf      equ 0ah              ; ASCII linefeed
cseg    segment para public 'CODE'
;
; ----  Set up DS, ES, and SS:SP to run as .COM  ----
;
        assume  cs:cseg
start   proc    far
        mov     ax,cs           ; set up segment registers
        add     ax,10h          ; AX = segment after PSP
        mov     ds,ax
        mov     ss,ax           ; set up stack pointer
        mov     sp,offset stk
        mov     ax,offset shell
        push    cs              ; push original CS
        push    ds              ; push segment of shell
        push    ax              ; push offset of shell
        ret                     ; jump to shell
start   endp
;
; ----  Main program running as .COM  ----
;
; CS, DS, SS = cseg
; Original CS value on top of stack
;
        assume cs:cseg,ds:cseg,ss:cseg
seg_size equ (((offset last) - (offset start)) + 10fh)/16
shell   proc    near
        pop     es              ; ES = segment to shrink
        mov     bx,seg_size     ; BX = new segment size
        mov     ah,4ah          ; AH = modify memory block
        int     21h             ; free excess memory
        mov     cmd_seg,ds      ; setup segments in
        mov     fcb1_seg,ds     ; parameter block for EXEC
        mov     fcb2_seg,ds
        mov     dx,offset main_loop
        mov     ax,2523h        ; AX = set Control-C handler
        int     21h             ; set handler to DS:DX
        mov     dx,offset main_loop
        mov     ax,2524h        ; AX = set critical error handler
        int     21h             ; set handler to DS:DX
                                ; Note: DS is equal to CS
main_loop:
        push    ds              ; save segment registers
        push    es
        mov     cs:stk_seg,ss   ; save stack pointer
        mov     cs:stk_off,sp
        mov     dx,offset pgm_name
        mov     bx,offset par_blk
        mov     ax,4b00h        ; AX = EXEC/run program
        int     21h             ; carry = EXEC failed
        mov     ss,cs:stk_seg   ; restore stack pointer
        mov     sp,cs:stk_off
        pop     es              ; restore segment registers
        pop     ds
        jnc     main_loop       ; loop if program run
        mov     dx,offset load_msg
        mov     cx,load_msg_length
        call    print           ; display error message
        mov     ah,08h          ; AH = read without echo
        int     21h             ; wait for any character
        jmp     main_loop       ; execute forever
shell   endp
;
; ----  Print string  ----
;
; DS:DX = address of string
; CX    = size
;
print   proc    near
        mov     ah,40h          ; AH = write to file
        mov     bx,stderr       ; BX = file handle
        int     21h             ; print string
        ret
print   endp
;
; ----  Message strings  ----
;
load_msg db cr,lf
         db 'Cannot load program.',cr,lf
         db 'Press any key to try again.',cr,lf
load_msg_length equ $-load_msg
;
; ----  Program data area  ----
;
stk_seg  dw     0               ; stack segment pointer
stk_off  dw     0               ; save area during EXEC
pgm_name db     '\NEWSHELL.COM',0 ; any program will do
par_blk  dw     0               ; use current environment
         dw     offset cmd_line ; command-line address
cmd_seg  dw     0               ; fill in at initialization
         dw     offset fcb1     ; default FCB #1
fcb1_seg dw     0               ; fill in at initialization
         dw     offset fcb2     ; default FCB #2
fcb2_seg dw     0               ; fill in at initialization
cmd_line db     0,cr            ; actual command line
fcb1     db     0
         db     11 dup (' ')
         db     25 dup ( 0 )
fcb2     db     0
         db     11 dup (' ')
         db     25 dup ( 0 )
         dw     200 dup ( 0 )   ; program stack area
stk      dw     0
last     equ    $               ; last address used
cseg     ends
         end    start



───────────────────────────────────────────────────────────────────────────


Figure 3-10.
Assembly-language routine to access a 12-bit FAT.


; ---- Obtain the next link number from a 12-bit FAT  -----
;
; Parameters:
;       ax     = current entry number
;       ds:bx  = address of FAT (must be contiguous)
;
; Returns:
;       ax     = next link number
;
; Uses: ax, bx, cx
next12  proc    near
        add     bx,ax           ; ds:bx = partial index
        shr     ax,1            ; ax = offset/2
                                ; carry = no shift needed
        pushf                   ; save carry
        add     bx,ax           ; ds:bx = next cluster number index
        mov     ax,[bx]         ; ax = next cluster number
        popf                    ; carry = no shift needed
        jc      shift           ; skip if using top 12 bits
        and     ax,0fffh        ; ax = lower 12 bits
        ret
shift:  mov     cx,4            ; cx = shift count
        shr     ax,cl           ; ax = top 12 bits in lower 12 bits
        ret
next12  endp



───────────────────────────────────────────────────────────────────────────


Figure 3-11.
Assembly-language routine to access a 16-bit FAT.


; ---- Obtain the next link number from a 16-bit FAT  -----
;
; Parameters:
;       ax     = current entry number
;       ds:bx  = address of FAT (must be contiguous)
;
; Returns:
;       ax    = next link number
;
; Uses: ax, bx, cx
next16  proc    near
        add     ax,ax           ; ax = word offset
        add     bx,ax           ; ds:bx = next link number index
        mov     ax,[bx]         ; ax = next link number
        ret
next16  endp



───────────────────────────────────────────────────────────────────────────


Figure 4-3.
Terminating properly under any MS-DOS version.


TEXT    SEGMENT PARA PUBLIC 'CODE'

        ASSUME  CS:TEXT,DS:NOTHING,ES:NOTHING,SS:NOTHING

TERM_VECTOR     DD      ?

ENTRY_PROC      PROC    FAR

;save pointer to termination vector in PSP

        MOV     WORD PTR CS:TERM_VECTOR+0,0000h ;save offset of Warm Boot
                                                ;vector
        MOV     WORD PTR CS:TERM_VECTOR+2,DS    ;save segment address of
                                                ;PSP
;***** Place main task here *****

;determine which MS-DOS version is active, take jump if 2.0 or later

        MOV     AH,30h          ;load Get MS-DOS Version Number function
                                ;code
        INT     21h             ;call MS-DOS to get version number
        OR      AL,AL           ;see if pre-2.0 MS-DOS
        JNZ     TERM_0200       ;jump if 2.0 or later

;terminate under pre-2.0 MS-DOS

        JMP     CS:TERM_VECTOR  ;jump to Warm Boot vector in PSP

;terminate under MS-DOS 2.0 or later

TERM_0200:
        MOV     AX,4C00h        ;load MS-DOS termination function code
                                ;and return code
        INT     21h             ;call MS-DOS to terminate

ENTRY_PROC      ENDP

TEXT    ENDS

        END     ENTRY_PROC      ;define entry point



───────────────────────────────────────────────────────────────────────────


Figure 4-4.
Using RET to return control to MS-DOS.


TEXT    SEGMENT PARA PUBLIC 'CODE'

        ASSUME  CS:TEXT,DS:NOTHING,ES:NOTHING,SS:NOTHING

ENTRY_PROC      PROC    FAR     ;make proc FAR so RET will be FAR

;Push pointer to termination vector in PSP
        PUSH    DS              ;push PSP's segment address
        XOR     AX,AX           ;ax = 0 = offset of Warm Boot vector in PSP
        PUSH    AX              ;push Warm Boot vector offset

;***** Place main task here *****

;Determine which MS-DOS version is active, take jump if 2.0 or later

        MOV     AH,30h          ;load Get MS-DOS Version Number function
                                ;code
        INT     21h             ;call MS-DOS to get version number
        OR      AL,AL           ;see if pre-2.0 MS-DOS
        JNZ     TERM_0200       ;jump if 2.0 or later

;Terminate under pre-2.0 MS-DOS (this is a FAR proc, so RET will be FAR)
        RET                     ;pop PSP:00H into CS:IP to terminate
;Terminate under MS-DOS 2.0 or later
TERM_0200:
        MOV     AX,4C00h        ;AH = MS-DOS Terminate Process with Return
                                ;Code
                                ;function code, AL = return code of 00H
        INT     21h             ;call MS-DOS to terminate

ENTRY_PROC      ENDP

TEXT    ENDS

        END     ENTRY_PROC      ;declare the program's entry point



───────────────────────────────────────────────────────────────────────────


Figure 4-8.
Structuring a .EXE program: MODULE_A.


;Source Module MODULE_A

;Predeclare all segments to force the linker's segment ordering ***********

_TEXT   SEGMENT BYTE PUBLIC 'CODE'
_TEXT   ENDS

_DATA   SEGMENT WORD PUBLIC 'DATA'
_DATA   ENDS

CONST   SEGMENT WORD PUBLIC 'CONST'
CONST   ENDS

_BSS    SEGMENT WORD PUBLIC 'BSS'
_BSS    ENDS

STACK   SEGMENT PARA STACK 'STACK'
STACK   ENDS

DGROUP  GROUP   _DATA,CONST,_BSS,STACK


;Constant declarations ****************************************************

CONST   SEGMENT WORD PUBLIC 'CONST'

CONST_FIELD_A   DB      'Constant A'    ;declare a MODULE_A constant

CONST   ENDS


;Preinitialized data fields ***********************************************

_DATA   SEGMENT WORD PUBLIC 'DATA'

DATA_FIELD_A    DB      'Data A'        ;declare a MODULE_A preinitialized
                                        ;field

_DATA   ENDS


;Uninitialized data fields ************************************************

_BSS    SEGMENT WORD PUBLIC 'BSS'

BSS_FIELD_A     DB      5 DUP(?)        ;declare a MODULE_A uninitialized
                                        ;field

_BSS    ENDS


;Program text *************************************************************

_TEXT   SEGMENT BYTE PUBLIC 'CODE'

        ASSUME  CS:_TEXT,DS:DGROUP,ES:NOTHING,SS:NOTHING

        EXTRN   PROC_B:NEAR             ;label is in _TEXT segment (NEAR)
        EXTRN   PROC_C:NEAR             ;label is in _TEXT segment (NEAR)

PROC_A  PROC    NEAR

        CALL    PROC_B                  ;call into MODULE_B
        CALL    PROC_C                  ;call into MODULE_C

        MOV     AX,4C00H                ;terminate (MS-DOS 2.0 or
                                        ;later only)
        INT     21H

PROC_A  ENDP

_TEXT   ENDS

;Stack ********************************************************************

STACK   SEGMENT PARA STACK 'STACK'

        DW      128 DUP(?)              ;declare some space to use as stack
STACK_BASE      LABEL   WORD

STACK   ENDS

        END     PROC_A                  ;declare PROC_A as entry point



───────────────────────────────────────────────────────────────────────────


Figure 4-9.
Structuring a .EXE program: MODULE_B.


;Source Module MODULE_B

;Constant declarations ****************************************************

CONST   SEGMENT WORD PUBLIC 'CONST'

CONST_FIELD_B   DB      'Constant B'    ;declare a MODULE_B constant

CONST   ENDS


;Preinitialized data fields ***********************************************

_DATA   SEGMENT WORD PUBLIC 'DATA'

DATA_FIELD_B    DB      'Data B'        ;declare a MODULE_B preinitialized
                                        ;field

_DATA   ENDS


;Uninitialized data fields ************************************************

_BSS    SEGMENT WORD PUBLIC 'BSS'

BSS_FIELD_B     DB      5 DUP(?)        ;declare a MODULE_B uninitialized
                                        ;field

_BSS    ENDS


;Program text *************************************************************

DGROUP  GROUP   _DATA,CONST,_BSS

_TEXT   SEGMENT BYTE PUBLIC 'CODE'

        ASSUME  CS:_TEXT,DS:DGROUP,ES:NOTHING,SS:NOTHING

        PUBLIC  PROC_B                  ;reference in MODULE_A
PROC_B  PROC    NEAR

        RET

PROC_B  ENDP

_TEXT   ENDS

        END



───────────────────────────────────────────────────────────────────────────


Figure 4-10.
Structuring a .EXE program: MODULE_C.


;Source Module MODULE_C

;Constant declarations ****************************************************

CONST   SEGMENT WORD PUBLIC 'CONST'

CONST_FIELD_C   DB      'Constant C'    ;declare a MODULE_C constant

CONST   ENDS


;Preinitialized data fields ***********************************************

_DATA   SEGMENT WORD PUBLIC 'DATA'

DATA_FIELD_C    DB      'Data C'        ;declare a MODULE_C preinitialized
                                        ;field

_DATA   ENDS


;Uninitialized data fields ************************************************

_BSS    SEGMENT WORD PUBLIC 'BSS'

BSS_FIELD_C     DB      5 DUP(?)        ;declare a MODULE_C uninitialized
                                        ;field

_BSS    ENDS


;Program text *************************************************************

DGROUP  GROUP   _DATA,CONST,_BSS

_TEXT   SEGMENT BYTE PUBLIC 'CODE'

        ASSUME  CS:_TEXT,DS:DGROUP,ES:NOTHING,SS:NOTHING

        PUBLIC  PROC_C                  ;referenced in MODULE_A
PROC_C  PROC    NEAR

        RET

PROC_C  ENDP

_TEXT   ENDS

        END



───────────────────────────────────────────────────────────────────────────


Figure 4-15.
.COM program with data at start.


COMSEG  SEGMENT BYTE PUBLIC 'CODE'
        ASSUME  CS:COMSEG,DS:COMSEG,ES:COMSEG,SS:COMSEG
        ORG     0100H

BEGIN:
        JMP     START           ;skip over data fields
;Place your data fields here.

START:
;Place your program text here.
        MOV     AX,4C00H        ;terminate (MS-DOS 2.0 or later only)
        INT     21H
COMSEG  ENDS
        END     BEGIN



───────────────────────────────────────────────────────────────────────────


Figure 4-16.
.COM program with data at end.


CSEG    SEGMENT BYTE PUBLIC 'CODE'      ;establish segment order
CSEG    ENDS
DSEG    SEGMENT BYTE PUBLIC 'DATA'
DSEG    ENDS
COMGRP  GROUP   CSEG,DSEG               ;establish joint address base
DSEG    SEGMENT
;Place your data fields here.
DSEG    ENDS
CSEG    SEGMENT

        ASSUME  CS:COMGRP,DS:COMGRP,ES:COMGRP,SS:COMGRP
        ORG     0100H

BEGIN:
;Place your program text here.  Remember to use
;OFFSET COMGRP:LABEL whenever you use OFFSET.
        MOV     AX,4C00H              ;terminate (MS-DOS 2.0 or later
                                      ;only)
        INT     21H
CSEG    ENDS
        END     BEGIN



───────────────────────────────────────────────────────────────────────────


Figure 6-1.
COMDVR.ASM.


   1 : Title   COMDVR  Driver for IBM COM Ports
   2 : ;       Jim Kyle, 1987
   3 : ;           Based on ideas from many sources......
   4 : ;               including Mike Higgins, CLM March 1985;
   5 : ;               public-domain INTBIOS program from BBS's;
   6 : ;               COMBIOS.COM from CIS Programmers' SIG; and
   7 : ;               ADVANCED MS-DOS by Ray Duncan.
   8 : Subttl  MS-DOS Driver Definitions
   9 :
  10 :         Comment *       This comments out the Dbg macro.....
  11 : Dbg     Macro   Ltr1,Ltr2,Ltr3  ; used only to debug driver...
  12 :         Local   Xxx
  13 :         Push    Es              ; save all regs used
  14 :         Push    Di
  15 :         Push    Ax
  16 :         Les     Di,Cs:Dbgptr    ; get pointer to CRT
  17 :         Mov     Ax,Es:[di]
  18 :         Mov     Al,Ltr1         ; move in letters
  19 :         Stosw
  20 :         Mov     Al,Ltr2
  21 :         Stosw
  22 :         Mov     Al,Ltr3
  23 :         Stosw
  24 :         Cmp     Di,1600         ; top 10 lines only
  25 :         Jb      Xxx
  26 :         Xor     Di,Di
  27 : Xxx:    Mov     Word Ptr Cs:Dbgptr,Di
  28 :         Pop     Ax
  29 :         Pop     Di
  30 :         Pop     Es
  31 :         Endm
  32 :         *                       ; asterisk ends commented-out region
  33 : ;
  34 : ;               Device Type Codes
  35 : DevChr  Equ     8000h   ; this is a character device
  36 : DevBlk  Equ     0000h   ; this is a block (disk) device
  37 : DevIoc  Equ     4000h   ; this device accepts IOCTL requests
  38 : DevNon  Equ     2000h   ; non-IBM disk driver (block only)
  39 : DevOTB  Equ     2000h   ; MS-DOS 3.x out until busy supported (char)
  40 : DevOCR  Equ     0800h   ; MS-DOS 3.x open/close/rm supported
  41 : DevX32  Equ     0040h   ; MS-DOS 3.2 functions supported
  42 : DevSpc  Equ     0010h   ; accepts special interrupt 29H
  43 : DevClk  Equ     0008h   ; this is the CLOCK device
  44 : DevNul  Equ     0004h   ; this is the NUL device
  45 : DevSto  Equ     0002h   ; this is standard output
  46 : DevSti  Equ     0001h   ; this is standard input
  47 : ;
  48 : ;               Error Status BITS
  49 : StsErr  Equ     8000h   ; general error
  50 : StsBsy  Equ     0200h   ; device busy
  51 : StsDne  Equ     0100h   ; request completed
  52 : ;
  53 : ;               Error Reason values for lower-order bits
  54 : ErrWp   Equ     0       ; write protect error
  55 : ErrUu   Equ     1       ; unknown unit
  56 : ErrDnr  Equ     2       ; drive not ready
  57 : ErrUc   Equ     3       ; unknown command
  58 : ErrCrc  Equ     4       ; cyclical redundancy check error
  59 : ErrBsl  Equ     5       ; bad drive request structure length
  60 : ErrSl   Equ     6       ; seek error
  61 : ErrUm   Equ     7       ; unknown media
  62 : ErrSnf  Equ     8       ; sector not found
  63 : ErrPop  Equ     9       ; printer out of paper
  64 : ErrWf   Equ     10      ; write fault
  65 : ErrRf   Equ     11      ; read fault
  66 : ErrGf   Equ     12      ; general failure
  67 : ;
  68 : ;       Structure of an I/O request packet header.
  69 : ;
  70 : Pack    Struc
  71 : Len     Db      ?       ; length of record
  72 : Prtno   Db      ?       ; unit code
  73 : Code    Db      ?       ; command code
  74 : Stat    Dw      ?       ; return status
  75 : Dosq    Dd      ?       ; (unused MS-DOS queue link pointer)
  76 : Devq    Dd      ?       ; (unused driver queue link pointer)
  77 : Media   Db      ?       ; media code on read/write
  78 : Xfer    Dw      ?       ; xfer address offset
  79 : Xseg    Dw      ?       ; xfer address segment
  80 : Count   Dw      ?       ; transfer byte count
  81 : Sector  Dw      ?       ; starting sector value (block only)
  82 : Pack    Ends
  83 :
  84 : Subttl  IBM-PC Hardware Driver Definitions
  85 : page
  86 : ;
  87 : ;               8259 data
  88 : PIC_b   Equ     020h    ; port for EOI
  89 : PIC_e   Equ     021h    ; port for Int enabling
  90 : EOI     Equ     020h    ; EOI control word
  91 : ;
  92 : ;               8250 port offsets
  93 : RxBuf   Equ     0F8h    ; base address
  94 : Baud1   Equ     RxBuf+1 ; baud divisor high byte
  95 : IntEn   Equ     RxBuf+1 ; interrupt enable register
  96 : IntId   Equ     RxBuf+2 ; interrupt identification register
  97 : Lctrl   Equ     RxBuf+3 ; line control register
  98 : Mctrl   Equ     RxBuf+4 ; modem control register
  99 : Lstat   Equ     RxBuf+5 ; line status register
 100 : Mstat   Equ     RxBuf+6 ; modem status register
 101 : ;
 102 : ;               8250 LCR constants
 103 : Dlab    Equ     10000000b ; divisor latch access bit
 104 : SetBrk  Equ     01000000b ; send break control bit
 105 : StkPar  Equ     00100000b ; stick parity control bit
 106 : EvnPar  Equ     00010000b ; even parity bit
 107 : GenPar  Equ     00001000b ; generate parity bit
 108 : Xstop   Equ     00000100b ; extra stop bit
 109 : Wd8     Equ     00000011b ; word length = 8
 110 : Wd7     Equ     00000010b ; word length = 7
 111 : Wd6     Equ     00000001b ; word length = 6
 112 : ;
 113 : ;               8250 LSR constants
 114 : xsre    Equ     01000000b ; xmt SR empty
 115 : xhre    Equ     00100000b ; xmt HR empty
 116 : BrkRcv  Equ     00010000b ; break received
 117 : FrmErr  Equ     00001000b ; framing error
 118 : ParErr  Equ     00000100b ; parity error
 119 : OveRun  Equ     00000010b ; overrun error
 120 : rdta    Equ     00000001b ; received data ready
 121 : AnyErr  Equ     BrkRcv+FrmErr+ParErr+OveRun
 122 : ;
 123 : ;               8250 MCR constants
 124 : LpBk    Equ     00010000b ; UART out loops to in (test)
 125 : Usr2    Equ     00001000b ; Gates 8250 interrupts
 126 : Usr1    Equ     00000100b ; aux user1 output
 127 : SetRTS  Equ     00000010b ; sets RTS output
 128 : SetDTR  Equ     00000001b ; sets DTR output
 129 : ;
 130 : ;               8250 MSR constants
 131 : CDlvl   Equ     10000000b ; carrier detect level
 132 : RIlvl   Equ     01000000b ; ring indicator level
 133 : DSRlvl  Equ     00100000b ; DSR level
 134 : CTSlvl  Equ     00010000b ; CTS level
 135 : CDchg   Equ     00001000b ; Carrier Detect change
 136 : RIchg   Equ     00000100b ; Ring Indicator change
 137 : DSRchg  Equ     00000010b ; DSR change
 138 : CTSchg  Equ     00000001b ; CTS change
 139 : ;
 140 : ;               8250 IER constants
 141 : S_Int   Equ     00001000b ; enable status interrupt
 142 : E_Int   Equ     00000100b ; enable error interrupt
 143 : X_Int   Equ     00000010b ; enable transmit interrupt
 144 : R_Int   Equ     00000001b ; enable receive interrupt
 145 : Allint  Equ     00001111b ; enable all interrupts
 146 :
 147 : Subttl  Definitions for THIS Driver
 148 : page
 149 : ;
 150 : ;               Bit definitions for the output status byte
 151 : ;                      ( this driver only )
 152 : LinIdl  Equ     0ffh    ; if all bits off, xmitter is idle
 153 : LinXof  Equ     1       ; output is suspended by XOFF
 154 : LinDSR  Equ     2       ; output is suspended until DSR comes on
                               ; again
 155 : LinCTS  Equ     4       ; output is suspended until CTS comes on
                               ; again
 156 : ;
 157 : ;               Bit definitions for the input status byte
 158 : ;                       ( this driver only )
 159 : BadInp  Equ     1       ; input line errors have been detected
 160 : LostDt  Equ     2       ; receiver buffer overflowed, data lost
 161 : OffLin  Equ     4       ; device is off line now
 162 : ;
 163 : ;               Bit definitions for the special characteristics
 164 : ;               words  ( this driver only )
 165 : ;               InSpec controls how input from the UART is treated
 166 : ;
 167 : InEpc   Equ     0001h   ; errors translate to codes with parity bit
                               ; on
 168 : ;
 169 : ;               OutSpec controls how output to the UART is treated
 170 : ;
 171 : OutDSR  Equ     0001h   ; DSR is used to throttle output data
 172 : OutCTS  Equ     0002h   ; CTS is used to throttle output data
 173 : OutXon  Equ     0004h   ; XON/XOFF is used to throttle output data
 174 : OutCdf  Equ     0010h   ; carrier detect is off-line signal
 175 : OutDrf  Equ     0020h   ; DSR is off-line signal
 176 : ;
 177 : Unit    Struc           ; each unit has a structure defining its
                               ; state:
 178 : Port    Dw      ?       ; I/O port address
 179 : Vect    Dw      ?       ; interrupt vector offset (NOT interrupt
                               ; number!)
 180 : Isradr  Dw      ?       ; offset to interrupt service routine
 181 : OtStat  Db      Wd8     ; default LCR bit settings during INIT,
 182 :                         ; output status bits after
 183 : InStat  Db      Usr2+SetRTS+SetDTR   ; MCR bit settings during INIT,
 184 :                         ; input status bits after
 185 : InSpec  Dw      InEpc   ; special mode bits for INPUT
 186 : OutSpec Dw      OutXon  ; special mode bits for OUTPUT
 187 : Baud    Dw      96      ; current baud rate divisor value (1200 b)
 188 : Ifirst  Dw      0       ; offset of first character in input buffer
 189 : Iavail  Dw      0       ; offset of next available byte
 190 : Ibuf    Dw      ?       ; pointer to input buffer
 191 : Ofirst  Dw      0       ; offset of first character in output buffer
 192 : Oavail  Dw      0       ; offset of next avail byte in output buffer
 193 : Obuf    Dw      ?       ; pointer to output buffer
 194 : Unit    Ends
 195 :
 196 : ;
 197 : ;       Beginning of driver code and data
 198 : ;
 199 : Driver  Segment
 200 :         Assume  Cs:driver, ds:driver, es:driver
 201 :         Org     0               ; drivers start at 0
 202 :
 203 :         Dw      Async2,-1       ; pointer to next device
 204 :         Dw      DevChr + DevIoc ; character device with IOCTL
 205 :         Dw      Strtegy         ; offset of Strategy routine
 206 :         Dw      Request1        ; offset of interrupt entry point 1
 207 :         Db      'ASY1    '      ; device 1 name
 208 : Async2:
 209 :         Dw      -1,-1           ; pointer to next device: MS-DOS
                                       ; fills in
 210 :         Dw      DevChr + DevIoc ; character device with IOCTL
 211 :         Dw      Strtegy         ; offset of Strategy routine
 212 :         Dw      Request2        ; offset of interrupt entry point 2
 213 :         Db      'ASY2    '      ; device 2 name
 214 :
 215 : ;dbgptr Dd      0b0000000h
 216 : ;
 217 : ;       Following is the storage area for the request packet pointer
 218 : ;
 219 : PackHd  Dd        0
 220 : ;
 221 : ;                 baud rate conversion table
 222 : Asy_baudt Dw           50,2304          ; first value is desired
                                               ; baud rate
 223 :           Dw           75,1536          ; second is divisor register
                                               ; value
 224 :           Dw          110,1047
 225 :           Dw          134, 857
 226 :           Dw          150, 786
 227 :           Dw          300, 384
 228 :           Dw          600, 192
 229 :           Dw         1200,  96
 230 :           Dw         1800,  64
 231 :           Dw         2000,  58
 232 :           Dw         2400,  48
 233 :           Dw         3600,  32
 234 :           Dw         4800,  24
 235 :           Dw         7200,  16
 236 :           Dw         9600,  12
 237 :
 238 : ; table of structures
 239 : ;       ASY1 defaults to the COM1 port, INT 0CH vector, XON,
 240 : ;       no parity, 8 databits, 1 stop bit, and 1200 baud
 241 : Asy_tab1:
 242 :         Unit    <3f8h,30h,asy1isr,,,,,,,,in1buf,,,out1buf>
 243 :
 244 : ;       ASY2 defaults to the COM2 port, INT 0BH vector, XON,
 245 : ;       no parity, 8 databits, 1 stop bit, and 1200 baud
 246 : Asy_tab2:
 247 :         Unit    <2f8h,2ch,asy2isr,,,,,,,,in2buf,,,out2buf>
 248 :
 249 : Bufsiz  Equ     256        ; input buffer size
 250 : Bufmsk  =       Bufsiz-1   ; mask for calculating offsets modulo
                                  ; bufsiz
 251 : In1buf  Db      Bufsiz DUP (?)
 252 : Out1buf Db      Bufsiz DUP (?)
 253 : In2buf  Db      Bufsiz DUP (?)
 254 : Out2buf Db      Bufsiz DUP (?)
 255 : ;
 256 : ;       Following is a table of offsets to all the driver  functions
 257 :
 258 : Asy_funcs:
 259 :         Dw      Init            ;  0 initialize driver
 260 :         Dw      Mchek           ;  1 media check (block only)
 261 :         Dw      BldBPB          ;  2 build BPB (block only)
 262 :         Dw      Ioctlin         ;  3 IOCTL read
 263 :         Dw      Read            ;  4 read
 264 :         Dw      Ndread          ;  5 nondestructive read
 265 :         Dw      Rxstat          ;  6 input status
 266 :         Dw      Inflush         ;  7 flush input buffer
 267 :         Dw      Write           ;  8 write
 268 :         Dw      Write           ;  9 write with verify
 269 :         Dw      Txstat          ; 10 output status
 270 :         Dw      Txflush         ; 11 flush output buffer
 271 :         Dw      Ioctlout        ; 12 IOCTL write
 272 : ; Following are not used in this driver.....
 273 :         Dw      Zexit           ; 13 open (3.x only, not used)
 274 :         Dw      Zexit           ; 14 close (3.x only, not used)
 275 :         Dw      Zexit           ; 15 rem med (3.x only, not used)
 276 :         Dw      Zexit           ; 16 out until bsy (3.x only, not
                                       ; used)
 277 :         Dw      Zexit           ; 17
 278 :         Dw      Zexit           ; 18
 279 :         Dw      Zexit           ; 19 generic IOCTL request (3.2
                                       ; only)
 280 :         Dw      Zexit           ; 20
 281 :         Dw      Zexit           ; 21
 282 :         Dw      Zexit           ; 22
 283 :         Dw      Zexit           ; 23 get logical drive map (3.2
                                       ; only)
 284 :         Dw      Zexit           ; 24 set logical drive map (3.2
                                       ; only)
 285 :
 286 : Subttl  Driver Code
 287 : Page
 288 : ;
 289 : ;       The Strategy routine itself:
 290 : ;
 291 : Strtegy Proc    Far
 292 : ;       dbg     'S','R',' '
 293 :         Mov     Word Ptr CS:PackHd,BX   ; store the offset
 294 :         Mov     Word Ptr CS:PackHd+2,ES ; store the segment
 295 :         Ret
 296 : Strtegy Endp
 297 : ;
 298 : Request1:                       ; async1 has been requested
 299 :         Push    Si              ; save SI
 300 :         Lea     Si,Asy_tab1     ; get the device unit table address
 301 :         Jmp     Short   Gen_request
 302 :
 303 : Request2:                       ; async2 has been requested
 304 :         Push    Si              ; save SI
 305 :         Lea     Si,Asy_tab2     ; get unit table two's address
 306 :
 307 : Gen_request:
 308 : ;       dbg     'R','R',' '
 309 :         Pushf                   ; save all regs
 310 :         Cld
 311 :         Push    Ax
 312 :         Push    Bx
 313 :         Push    Cx
 314 :         Push    Dx
 315 :         Push    Di
 316 :         Push    Bp
 317 :         Push    Ds
 318 :         Push    Es
 319 :         Push    Cs              ; set DS = CS
 320 :         Pop     Ds
 321 :         Les     Bx,PackHd       ; get packet pointer
 322 :         Lea     Di,Asy_funcs    ; point DI to jump table
 323 :         Mov     Al,es:code[bx]  ; command code
 324 :         Cbw
 325 :         Add     Ax,Ax           ; double to word
 326 :         Add     Di,ax
 327 :         Jmp     [di]            ; go do it
 328 : ;
 329 : ;       Exit from driver request
 330 : ;
 331 : ExitP   Proc    Far
 332 : Bsyexit:
 333 :         Mov     Ax,StsBsy
 334 :         Jmp     Short   Exit
 335 :
 336 : Mchek:
 337 : BldBPB:
 338 : Zexit:  Xor     Ax,Ax
 339 : Exit:   Les     Bx,PackHd       ; get packet pointer
 340 :         Or      Ax,StsDne
 341 :         Mov     Es:Stat[Bx],Ax  ; set return status
 342 :         Pop     Es              ; restore registers
 343 :         Pop     Ds
 344 :         Pop     Bp
 345 :         Pop     Di
 346 :         Pop     Dx
 347 :         Pop     Cx
 348 :         Pop     Bx
 349 :         Pop     Ax
 350 :         Popf
 351 :         Pop     Si
 352 :         Ret
 353 : ExitP   Endp
 354 :
 355 : Subttl  Driver Service Routines
 356 : Page
 357 :
 358 : ;       Read data from device
 359 :
 360 : Read:
 361 : ;       dbg     'R','d',' '
 362 :         Mov     Cx,Es:Count[bx] ; get requested nbr
 363 :         Mov     Di,Es:Xfer[bx]  ; get target pointer
 364 :         Mov     Dx,Es:Xseg[bx]
 365 :         Push    Bx              ; save for count fixup
 366 :         Push    Es
 367 :         Mov     Es,Dx
 368 :         Test    InStat[si],BadInp Or LostDt
 369 :         Je      No_lerr         ; no error so far...
 370 :         Add     Sp,4            ; error, flush SP
 371 :         And     InStat[si],Not ( BadInp Or LostDt )
 372 :         Mov     Ax,ErrRf        ; error, report it
 373 :         Jmp     Exit
 374 : No_lerr:
 375 :         Call    Get_in          ; go for one
 376 :         Or      Ah,Ah
 377 :         Jnz     Got_all         ; none to get now
 378 :         Stosb                   ; store it
 379 :         Loop    No_lerr         ; go for more
 380 : Got_all:
 381 :         Pop     Es
 382 :         Pop     Bx
 383 :         Sub     Di,Es:Xfer[bx]  ; calc number stored
 384 :         Mov     Es:Count[bx],Di ; return as count
 385 :         Jmp     Zexit
 386 :
 387 : ;       Nondestructive read from device
 388 :
 389 : Ndread:
 390 :         Mov     Di,ifirst[si]
 391 :         Cmp     Di,iavail[si]
 392 :         Jne     Ndget
 393 :         Jmp     Bsyexit         ; buffer empty
 394 : Ndget:
 395 :         Push    Bx
 396 :         Mov     Bx,ibuf[si]
 397 :         Mov     Al,[bx+di]
 398 :         Pop     Bx
 399 :         Mov     Es:media[bx],al ; return char
 400 :         Jmp     Zexit
 401 :
 402 : ;       Input status request
 403 :
 404 : Rxstat:
 405 :         Mov     Di,ifirst[si]
 406 :         Cmp     Di,iavail[si]
 407 :         Jne     Rxful
 408 :         Jmp     Bsyexit         ; buffer empty
 409 : Rxful:
 410 :         Jmp     Zexit           ; have data
 411 :
 412 : ;       Input flush request
 413 :
 414 : Inflush:
 415 :         Mov     Ax,iavail[si]
 416 :         Mov     Ifirst[si],ax
 417 :         Jmp     Zexit
 418 :
 419 : ;       Output data to device
 420 :
 421 : Write:
 422 : ;       dbg     'W','r',' '
 423 :         Mov     Cx,es:count[bx]
 424 :         Mov     Di,es:xfer[bx]
 425 :         Mov     Ax,es:xseg[bx]
 426 :         Mov     Es,ax
 427 : Wlup:
 428 :         Mov     Al,es:[di]      ; get the byte
 429 :         Inc     Di
 430 : Wwait:
 431 :         Call    Put_out         ; put away
 432 :         Cmp     Ah,0
 433 :         Jne     Wwait           ; wait for room!
 434 :         Call    Start_output    ; get it going
 435 :         Loop    Wlup
 436 :
 437 :         Jmp     Zexit
 438 :
 439 : ;       Output status request
 440 :
 441 : Txstat:
 442 :         Mov     Ax,ofirst[si]
 443 :         Dec     Ax
 444 :         And     Ax,bufmsk
 445 :         Cmp     Ax,oavail[si]
 446 :         Jne     Txroom
 447 :         Jmp     Bsyexit         ; buffer full
 448 : Txroom:
 449 :         Jmp     Zexit           ; room exists
 450 :
 451 : ;       IOCTL read request, return line parameters
 452 :
 453 : Ioctlin:
 454 :         Mov     Cx,es:count[bx]
 455 :         Mov     Di,es:xfer[bx]
 456 :         Mov     Dx,es:xseg[bx]
 457 :         Mov     Es,dx
 458 :         Cmp     Cx,10
 459 :         Je      Doiocin
 460 :         Mov     Ax,errbsl
 461 :         Jmp     Exit
 462 : Doiocin:
 463 :         Mov     Dx,port[si]     ; base port
 464 :         Mov     Dl,Lctrl        ; line status
 465 :         Mov     Cx,4            ; LCR, MCR, LSR, MSR
 466 : Getport:
 467 :         In      Al,dx
 468 :         Stos    Byte Ptr [DI]
 469 :         Inc     Dx
 470 :         Loop    Getport
 471 :
 472 :         Mov     Ax,InSpec[si]   ; spec in flags
 473 :         Stos    Word Ptr [DI]
 474 :         Mov     Ax,OutSpec[si]  ; out flags
 475 :         Stos    Word Ptr [DI]
 476 :         Mov     Ax,baud[si]     ; baud rate
 477 :         Mov     Bx,di
 478 :         Mov     Di,offset Asy_baudt+2
 479 :         Mov     Cx,15
 480 : Baudcin:
 481 :         Cmp     [di],ax
 482 :         Je      Yesinb
 483 :         Add     Di,4
 484 :         Loop    Baudcin
 485 : Yesinb:
 486 :         Mov     Ax,-2[di]
 487 :         Mov     Di,bx
 488 :         Stos    Word Ptr [DI]
 489 :         Jmp     Zexit
 490 :
 491 : ;       Flush output buffer request
 492 :
 493 : Txflush:
 494 :         Mov     Ax,oavail[si]
 495 :         Mov     Ofirst[si],ax
 496 :         Jmp     Zexit
 497 :
 498 : ;       IOCTL request: change line parameters for this driver
 499 :
 500 : Ioctlout:
 501 :         Mov     Cx,es:count[bx]
 502 :         Mov     Di,es:xfer[bx]
 503 :         Mov     Dx,es:xseg[bx]
 504 :         Mov     Es,dx
 505 :         Cmp     Cx,10
 506 :         Je      Doiocout
 507 :         Mov     Ax,errbsl
 508 :         Jmp     Exit
 509 :
 510 : Doiocout:
 511 :         Mov     Dx,port[si]     ; base port
 512 :         Mov     Dl,Lctrl        ; line ctrl
 513 :         Mov     Al,es:[di]
 514 :         Inc     Di
 515 :         Or      Al,Dlab         ; set baud
 516 :         Out     Dx,al
 517 :         Clc
 518 :         Jnc     $+2
 519 :         Inc     Dx              ; mdm ctrl
 520 :         Mov     Al,es:[di]
 521 :         Or      Al,Usr2         ; Int Gate
 522 :         Out     Dx,al
 523 :         Add     Di,3            ; skip LSR,MSR
 524 :         Mov     Ax,es:[di]
 525 :         Add     Di,2
 526 :         Mov     InSpec[si],ax
 527 :         Mov     Ax,es:[di]
 528 :         Add     Di,2
 529 :         Mov     OutSpec[si],ax
 530 :         Mov     Ax,es:[di]      ; set baud
 531 :         Mov     Bx,di
 532 :         Mov     Di,offset Asy_baudt
 533 :         Mov     Cx,15
 534 : Baudcout:
 535 :         Cmp     [di],ax
 536 :         Je      Yesoutb
 537 :         Add     Di,4
 538 :         Loop    Baudcout
 539 :
 540 :         Mov     Dl,Lctrl        ; line ctrl
 541 :         In      Al,dx           ; get LCR data
 542 :         And     Al,not Dlab     ; strip
 543 :         Clc
 544 :         Jnc     $+2
 545 :         Out     Dx,al           ; put back
 546 :         Mov     Ax,ErrUm        ; "unknown media"
 547 :         Jmp     Exit
 548 :
 549 : Yesoutb:
 550 :         Mov     Ax,2[di]        ; get divisor
 551 :         Mov     Baud[si],ax     ; save to report later
 552 :         Mov     Dx,port[si]     ; set divisor
 553 :         Out     Dx,al
 554 :         Clc
 555 :         Jnc     $+2
 556 :         Inc     Dx
 557 :         Mov     Al,ah
 558 :         Out     Dx,al
 559 :         Clc
 560 :         Jnc     $+2
 561 :         Mov     Dl,Lctrl        ; line ctrl
 562 :         In      Al,dx           ; get LCR data
 563 :         And     Al,not Dlab     ; strip
 564 :         Clc
 565 :         Jnc     $+2
 566 :         Out     Dx,al           ; put back
 567 :         Jmp     Zexit
 568 :
 569 : Subttl  Ring Buffer Routines
 570 : Page
 571 :
 572 : Put_out Proc    Near    ; puts AL into output ring buffer
 573 :         Push    Cx
 574 :         Push    Di
 575 :         Pushf
 576 :         Cli
 577 :         Mov     Cx,oavail[si]   ; put ptr
 578 :         Mov     Di,cx
 579 :         Inc     Cx              ; bump
 580 :         And     Cx,bufmsk
 581 :         Cmp     Cx,ofirst[si]   ; overflow?
 582 :         Je      Poerr           ; yes, don't
 583 :         Add     Di,obuf[si]     ; no
 584 :         Mov     [di],al         ; put in buffer
 585 :         Mov     Oavail[si],cx
 586 : ;       dbg     'p','o',' '
 587 :         Mov     Ah,0
 588 :         Jmp     Short   Poret
 589 : Poerr:
 590 :         Mov     Ah,-1
 591 : Poret:
 592 :         Popf
 593 :         Pop     Di
 594 :         Pop     Cx
 595 :         Ret
 596 : Put_out Endp
 597 :
 598 : Get_out Proc    Near    ; gets next character from output ring
                               ; buffer
 599 :         Push    Cx
 600 :         Push    Di
 601 :         Pushf
 602 :         Cli
 603 :         Mov     Di,ofirst[si]   ; get ptr
 604 :         Cmp     Di,oavail[si]   ; put ptr
 605 :         Jne     Ngoerr
 606 :         Mov     Ah,-1           ; empty
 607 :         Jmp     Short   Goret
 608 : Ngoerr:
 609 : ;       dbg     'g','o',' '
 610 :         Mov     Cx,di
 611 :         Add     Di,obuf[si]
 612 :         Mov     Al,[di]         ; get char
 613 :         Mov     Ah,0
 614 :         Inc     Cx              ; bump ptr
 615 :         And     Cx,bufmsk       ; wrap
 616 :         Mov     Ofirst[si],cx
 617 : Goret:
 618 :         Popf
 619 :         Pop     Di
 620 :         Pop     Cx
 621 :         Ret
 622 : Get_out Endp
 623 :
 624 : Put_in  Proc    Near    ; puts the char from AL into input ring
                               ; buffer
 625 :         Push    Cx
 626 :         Push    Di
 627 :         Pushf
 628 :         Cli
 629 :         Mov     Di,iavail[si]
 630 :         Mov     Cx,di
 631 :         Inc     Cx
 632 :         And     Cx,bufmsk
 633 :         Cmp     Cx,ifirst[si]
 634 :         Jne     Npierr
 635 :         Mov     Ah,-1
 636 :         Jmp     Short   Piret
 637 : Npierr:
 638 :         Add     Di,ibuf[si]
 639 :         Mov     [di],al
 640 :         Mov     Iavail[si],cx
 641 : ;       dbg     'p','i',' '
 642 :         Mov     Ah,0
 643 : Piret:
 644 :         Popf
 645 :         Pop     Di
 646 :         Pop     Cx
 647 :         Ret
 648 : Put_in  Endp
 649 :
 650 : Get_in  Proc    Near    ; gets one from input ring buffer into AL
 651 :         Push    Cx
 652 :         Push    Di
 653 :         Pushf
 654 :         Cli
 655 :         Mov     Di,ifirst[si]
 656 :         Cmp     Di,iavail[si]
 657 :         Je      Gierr
 658 :         Mov     Cx,di
 659 :         Add     Di,ibuf[si]
 660 :         Mov     Al,[di]
 661 :         Mov     Ah,0
 662 : ;       dbg     'g','i',' '
 663 :         Inc     Cx
 664 :         And     Cx,bufmsk
 665 :         Mov     Ifirst[si],cx
 666 :         Jmp     Short   Giret
 667 : Gierr:
 668 :         Mov     Ah,-1
 669 : Giret:
 670 :         Popf
 671 :         Pop     Di
 672 :         Pop     Cx
 673 :         Ret
 674 : Get_in  Endp
 675 :
 676 : Subttl  Interrupt Dispatcher Routine
 677 : Page
 678 :
 679 : Asy1isr:
 680 :         Sti
 681 :         Push    Si
 682 :         Lea     Si,asy_tab1
 683 :         Jmp     Short   Int_serve
 684 :
 685 : Asy2isr:
 686 :         Sti
 687 :         Push    Si
 688 :         Lea     Si,asy_tab2
 689 :
 690 : Int_serve:
 691 :         Push    Ax              ; save all regs
 692 :         Push    Bx
 693 :         Push    Cx
 694 :         Push    Dx
 695 :         Push    Di
 696 :         Push    Ds
 697 :         Push    Cs              ; set DS = CS
 698 :         Pop     Ds
 699 : Int_exit:
 700 : ;       dbg     'I','x',' '
 701 :         Mov     Dx,Port[si]     ; base address
 702 :         Mov     Dl,IntId        ; check Int ID
 703 :         In      Al,Dx
 704 :         Cmp     Al,00h          ; dispatch filter
 705 :         Je      Int_modem
 706 :         Jmp     Int_mo_no
 707 : Int_modem:
 708 : ;       dbg     'M','S',' '
 709 :         Mov     Dl,Mstat
 710 :         In      Al,dx           ; read MSR content
 711 :         Test    Al,CDlvl        ; carrier present?
 712 :         Jnz     Msdsr           ; yes, test for DSR
 713 :         Test    OutSpec[si],OutCdf      ; no, is CD off line?
 714 :         Jz      Msdsr
 715 :         Or      InStat[si],OffLin
 716 : Msdsr:
 717 :         Test    Al,DSRlvl       ; DSR present?
 718 :         Jnz     Dsron           ; yes, handle it
 719 :         Test    OutSpec[si],OutDSR      ; no, is DSR throttle?
 720 :         Jz      Dsroff
 721 :         Or      OtStat[si],LinDSR       ; yes, throttle down
 722 : Dsroff:
 723 :         Test    OutSpec[si],OutDrf      ; is DSR off line?
 724 :         Jz      Mscts
 725 :         Or      InStat[si],OffLin       ; yes, set flag
 726 :         Jmp     Short   Mscts
 727 : Dsron:
 728 :         Test    OtStat[si],LinDSR       ; throttled for DSR?
 729 :         Jz      Mscts
 730 :         Xor     OtStat[si],LinDSR       ; yes, clear it out
 731 :         Call    Start_output
 732 : Mscts:
 733 :         Test    Al,CTSlvl       ; CTS present?
 734 :         Jnz     Ctson           ; yes, handle it
 735 :         Test    OutSpec[si],OutCTS      ; no, is CTS throttle?
 736 :         Jz      Int_exit2
 737 :         Or      OtStat[si],LinCTS       ; yes, shut it down
 738 :         Jmp     Short   Int_exit2
 739 : Ctson:
 740 :         Test    OtStat[si],LinCTS       ; throttled for CTS?
 741 :         Jz      Int_exit2
 742 :         Xor     OtStat[si],LinCTS       ; yes, clear it out
 743 :         Jmp     Short   Int_exit1
 744 : Int_mo_no:
 745 :         Cmp     Al,02h
 746 :         Jne     Int_tx_no
 747 : Int_txmit:
 748 : ;       dbg     'T','x',' '
 749 : Int_exit1:
 750 :         Call    Start_output    ; try to send another
 751 : Int_exit2:
 752 :         Jmp     Int_exit
 753 : Int_tx_no:
 754 :         Cmp     Al,04h
 755 :         Jne     Int_rec_no
 756 : Int_receive:
 757 : ;       dbg     'R','x',' '
 758 :         Mov     Dx,port[si]
 759 :         In      Al,dx           ; take char from 8250
 760 :         Test    OutSpec[si],OutXon  ; is XON/XOFF enabled?
 761 :         Jz      Stuff_in        ; no
 762 :         Cmp     Al,'S' And 01FH ; yes, is this XOFF?
 763 :         Jne     Isq             ; no, check for XON
 764 :         Or      OtStat[si],LinXof ; yes, disable output
 765 :         Jmp     Int_exit2       ; don't store this one
 766 : Isq:
 767 :         Cmp     Al,'Q' And 01FH ; is this XON?
 768 :         Jne     Stuff_in        ; no, save it
 769 :         Test    OtStat[si],LinXof ; yes, waiting?
 770 :         Jz      Int_exit2       ; no, ignore it
 771 :         Xor     OtStat[si],LinXof ; yes, clear the XOFF bit
 772 :         Jmp     Int_exit1       ; and try to resume xmit
 773 : Int_rec_no:
 774 :         Cmp     Al,06h
 775 :         Jne     Int_done
 776 : Int_rxstat:
 777 : ;       dbg     'E','R',' '
 778 :         Mov     Dl,Lstat
 779 :         In      Al,dx
 780 :         Test    InSpec[si],InEpc ; return them as codes?
 781 :         Jz      Nocode          ; no, just set error alarm
 782 :         And     Al,AnyErr       ; yes, mask off all but error bits
 783 :         Or      Al,080h
 784 : Stuff_in:
 785 :         Call    Put_in          ; put input char in buffer
 786 :         Cmp     Ah,0            ; did it fit?
 787 :         Je      Int_exit3       ; yes, all OK
 788 :         Or      InStat[si],LostDt  ; no, set DataLost bit
 789 : Int_exit3:
 790 :         Jmp     Int_exit
 791 : Nocode:
 792 :         Or      InStat[si],BadInp
 793 :         Jmp     Int_exit3
 794 : Int_done:
 795 :         Clc
 796 :         Jnc     $+2
 797 :         Mov     Al,EOI          ; all done now
 798 :         Out     PIC_b,Al
 799 :         Pop     Ds              ; restore regs
 800 :         Pop     Di
 801 :         Pop     Dx
 802 :         Pop     Cx
 803 :         Pop     Bx
 804 :         Pop     Ax
 805 :         Pop     Si
 806 :         Iret
 807 :
 808 : Start_output    Proc    Near
 809 :         Test    OtStat[si],LinIdl ; Blocked?
 810 :         Jnz     Dont_start      ; yes, no output
 811 :         Mov     Dx,port[si]     ; no, check UART
 812 :         Mov     Dl,Lstat
 813 :         In      Al,Dx
 814 :         Test    Al,xhre         ; empty?
 815 :         Jz      Dont_start      ; no
 816 :         Call    Get_out         ; yes, anything waiting?
 817 :         Or      Ah,Ah
 818 :         Jnz     Dont_start      ; no
 819 :         Mov     Dl,RxBuf        ; yes, send it out
 820 :         Out     Dx,al
 821 : ;       dbg     's','o',' '
 822 : Dont_start:
 823 :         ret
 824 : Start_output    Endp
 825 :
 826 : Subttl  Initialization Request Routine
 827 : Page
 828 :
 829 : Init:   Lea     Di,$            ; release rest...
 830 :         Mov     Es:Xfer[bx],Di
 831 :         Mov     Es:Xseg[bx],Cs
 832 :
 833 :         Mov     Dx,Port[si]     ; base port
 834 :         Mov     Dl,Lctrl
 835 :         Mov     Al,Dlab         ; enable divisor
 836 :         Out     Dx,Al
 837 :         Clc
 838 :         Jnc     $+2
 839 :         Mov     Dl,RxBuf
 840 :         Mov     Ax,Baud[si]     ; set baud
 841 :         Out     Dx,Al
 842 :         Clc
 843 :         Jnc     $+2
 844 :         Inc     Dx
 845 :         Mov     Al,Ah
 846 :         Out     Dx,Al
 847 :         Clc
 848 :         Jnc     $+2
 849 :
 850 :         Mov     Dl,Lctrl        ; set LCR
 851 :         Mov     Al,OtStat[si]   ; from table
 852 :         Out     Dx,Al
 853 :         Mov     OtStat[si],0    ; clear status
 854 :         Clc
 855 :         Jnc     $+2
 856 :         Mov     Dl,IntEn        ; IER
 857 :         Mov     Al,AllInt       ; enable ints in 8250
 858 :         Out     Dx,Al
 859 :         Clc
 860 :         Jnc     $+2
 861 :         Mov     Dl,Mctrl        ; set MCR
 862 :         Mov     Al,InStat[si]   ; from table
 863 :         Out     Dx,Al
 864 :         Mov     InStat[si],0    ; clear status
 865 :
 866 : ClRgs:  Mov     Dl,Lstat        ; clear LSR
 867 :         In      Al,Dx
 868 :         Mov     Dl,RxBuf        ; clear RX reg
 869 :         In      Al,Dx
 870 :         Mov     Dl,Mstat        ; clear MSR
 871 :         In      Al,Dx
 872 :         Mov     Dl,IntId        ; IID reg
 873 :         In      Al,Dx
 874 :         In      Al,Dx
 875 :         Test    Al,1            ; int pending?
 876 :         Jz      ClRgs           ; yes, repeat
 877 :
 878 :         Cli
 879 :         Xor     Ax,Ax           ; set int vec
 880 :         Mov     Es,Ax
 881 :         Mov     Di,Vect[si]
 882 :         Mov     Ax,IsrAdr[si]   ; from table
 883 :         Stosw
 884 :         Mov     Es:[di],cs
 885 :
 886 :         In      Al,PIC_e        ; get 8259
 887 :         And     Al,0E7h         ; com1/2 mask
 888 :         Clc
 889 :         Jnb     $+2
 890 :         Out     PIC_e,Al
 891 :         Sti
 892 :
 893 :         Mov     Al,EOI          ; now send EOI just in case
 894 :         Out     PIC_b,Al
 895 :
 896 : ;       dbg     'D','I',' '     ; driver installed
 897 :         Jmp     Zexit
 898 :
 899 : Driver  Ends
 900 :         End
→


───────────────────────────────────────────────────────────────────────────


Figure 6-3.
ENGINE.ASM.


   1 :         TITLE   engine
   2 :
   3 : CODE    SEGMENT PUBLIC 'CODE'
   4 :
   5 :         ASSUME  CS:CODE,DS:CODE,ES:CODE,SS:CODE
   6 :
   7 :         ORG     0100h
   8 :
   9 : START:  mov     dx,offset devnm ; open named device (ASY1)
  10 :         mov     ax,3d02h
  11 :         int     21h
  12 :         mov     handle,ax       ; save the handle
  13 :         jc      quit
  14 : alltim: call    getmdm          ; main engine loop
  15 :         call    putcrt
  16 :         call    getkbd
  17 :         call    putmdm
  18 :         jmp     alltim
  19 : quit:   mov     ah,4ch          ; come here to quit
  20 :         int     21h
  21 :
  22 : getmdm  proc                    ; get input from modem
  23 :         mov     cx,256
  24 :         mov     bx,handle
  25 :         mov     dx,offset mbufr
  26 :         mov     ax,3F00h
  27 :         int     21h
  28 :         jc      quit
  29 :         mov     mdlen,ax
  30 :         ret
  31 : getmdm  endp
  32 :
  33 : getkbd  proc                    ; get input from keyboard
  34 :         mov     kblen,0         ; first zero the count
  35 :         mov     ah,11           ; key pressed?
  36 :         int     21h
  37 :         inc     al
  38 :         jnz     nogk            ; no
  39 :         mov     ah,7            ; yes, get it
  40 :         int     21h
  41 :         cmp     al,3            ; was it Ctrl-C?
  42 :         je      quit            ; yes, get out
  43 :         mov     kbufr,al        ; no, save it
  44 :         inc     kblen
  45 :         cmp     al,13           ; was it Enter?
  46 :         jne     nogk            ; no
  47 :         mov     byte ptr kbufr+1,10 ; yes, add LF
  48 :         inc     kblen
  49 : nogk:   ret
  50 : getkbd  endp
  51 :
  52 : putmdm  proc                    ; put output to modem
  53 :         mov     cx,kblen
  54 :         jcxz    nopm
  55 :         mov     bx,handle
  56 :         mov     dx,offset kbufr
  57 :         mov     ax,4000h
  58 :         int     21h
  59 :         jc      quit
  60 : nopm:   ret
  61 : putmdm  endp
  62 :
  63 : putcrt  proc                    ; put output to CRT
  64 :         mov     cx,mdlen
  65 :         jcxz    nopc
  66 :         mov     bx,1
  67 :         mov     dx,offset mbufr
  68 :         mov     ah,40h
  69 :         int     21h
  70 :         jc      quit
  71 : nopc:   ret
  72 : putcrt  endp
  73 :
  74 : devnm   db      'ASY1',0        ; miscellaneous data and buffers
  75 : handle  dw      0
  76 : kblen   dw      0
  77 : mdlen   dw      0
  78 : mbufr   db      256 dup (0)
  79 : kbufr   db      80 dup (0)
  80 :
  81 : CODE    ENDS
  82 :         END     START



───────────────────────────────────────────────────────────────────────────


Figure 6-4.
CDVUTL.C


  1 : /* cdvutl.c - COMDVR Utility
  2 :  *     Jim Kyle - 1987
  3 :  *     for use with COMDVR.SYS Device Driver
  4 :  */
  5 :
  6 : #include <stdio.h>                 /* i/o definitions      */
  7 : #include <conio.h>                 /* special console i/o  */
  8 : #include <stdlib.h>                /* misc definitions     */
  9 : #include <dos.h>                   /* defines intdos()     */
 10 :
 11 : /*       the following define the driver status bits       */
 12 :
 13 : #define HWINT 0x0800           /* MCR, first word, HW Ints gated */
 14 : #define o_DTR 0x0200           /* MCR, first word, output DTR    */
 15 : #define o_RTS 0x0100           /* MCR, first word, output RTS    */
 16 :
 17 : #define m_PG  0x0010           /* LCR, first word, parity ON     */
 18 : #define m_PE  0x0008           /* LCR, first word, parity EVEN   */
 19 : #define m_XS  0x0004           /* LCR, first word, 2 stop bits   */
 20 : #define m_WL  0x0003           /* LCR, first word, wordlen mask  */
 21 :
 22 : #define i_CD  0x8000           /* MSR, 2nd word, Carrier Detect  */
 23 : #define i_RI  0x4000           /* MSR, 2nd word, Ring Indicator  */
 24 : #define i_DSR 0x2000           /* MSR, 2nd word, Data Set Ready  */
 25 : #define i_CTS 0x1000           /* MSR, 2nd word, Clear to Send   */
 26 :
 27 : #define l_SRE 0x0040           /* LSR, 2nd word, Xmtr SR Empty   */
 28 : #define l_HRE 0x0020           /* LSR, 2nd word, Xmtr HR Empty   */
 29 : #define l_BRK 0x0010           /* LSR, 2nd word, Break Received  */
 30 : #define l_ER1 0x0008           /* LSR, 2nd word, FrmErr          */
 31 : #define l_ER2 0x0004           /* LSR, 2nd word, ParErr          */
 32 : #define l_ER3 0x0002           /* LSR, 2nd word, OveRun          */
 33 : #define l_RRF 0x0001           /* LSR, 2nd word, Rcvr DR Full    */
 34 :
 35 : /*           now define CLS string for ANSI.SYS                  */
 36 : #define CLS    "\033[2J"
 37 :
 38 : FILE * dvp;
 39 : union REGS rvs;
 40 : int iobf [ 5 ];
 41 :
 42 : main ()
 43 : { cputs ( "\nCDVUTL - COMDVR Utility Version 1.0 - 1987\n" );
 44 :   disp ();                         /* do dispatch loop           */
 45 : }
 46 :
 47 : disp ()                            /* dispatcher; infinite loop  */
 48 : { int c,
 49 :     u;
 50 :   u = 1;
 51 :   while ( 1 )
 52 :     { cputs ( "\r\n\tCommand (? for help): " );
 53 :       switch ( tolower ( c = getche ()))   /* dispatch           */
 54 :         {
 55 :         case '1' :                 /* select port 1              */
 56 :           fclose ( dvp );
 57 :           dvp = fopen ( "ASY1", "rb+" );
 58 :              u = 1;
 59 :           break;
 60 :
 61 :         case '2' :                 /* select port 2              */
 62 :           fclose ( dvp );
 63 :           dvp = fopen ( "ASY2", "rb+" );
 64 :           u = 2;
 65 :           break;
 66 :
 67 :         case 'b' :                 /* set baud rate              */
 68 :           if ( iobf [ 4 ] == 300 )
 69 :             iobf [ 4 ] = 1200;
 70 :           else
 71 :             if ( iobf [ 4 ] == 1200 )
 72 :               iobf [ 4 ] = 2400;
 73 :           else
 74 :             if ( iobf [ 4 ] == 2400 )
 75 :               iobf [ 4 ] = 9600;
 76 :           else
 77 :             iobf [ 4 ] = 300;
 78 :           iocwr ();
 79 :           break;
 80 :
 81 :         case 'e' :                 /* set parity even            */
 82 :           iobf [ 0 ] = ( m_PG + m_PE );
 83 :           iocwr ();
 84 :           break;
 85 :
 86 :         case 'f' :                 /* toggle flow control        */
 87 :           if ( iobf [ 3 ] == 1 )
 88 :             iobf [ 3 ] = 2;
 89 :           else
 90 :             if ( iobf [ 3 ] == 2 )
 91 :               iobf [ 3 ] = 4;
 92 :           else
 93 :             if ( iobf [ 3 ] == 4 )
 94 :               iobf [ 3 ] = 0;
 95 :           else
 96 :             iobf [ 3 ] = 1;
 97 :           iocwr ();
 98 :           break;
 99 :
100 :         case 'i' :                 /* initialize MCR/LCR to 8N1 : */
101 :           iobf [ 0 ] = ( HWINT + o_DTR + o_RTS + m_WL );
102 :           iocwr ();
103 :           break;
104 :
105 :         case '?' :                 /* this help list             */
106 :           cputs ( CLS );           /* clear the display          */
107 :          center ( "COMMAND LIST \n" );
108 : center ( "1 = select port 1           L = toggle word LENGTH  " );
109 : center ( "2 = select port 2           N = set parity to NONE  " );
110 : center ( "B = set BAUD rate           O = set parity to ODD   " );
111 : center ( "E = set parity to EVEN      R = toggle error REPORTS" );
112 : center ( "F = toggle FLOW control     S = toggle STOP bits    " );
113 : center ( "I = INITIALIZE ints, etc.   Q = QUIT                " );
114 :          continue;
115 :
116 :         case 'l' :                 /* toggle word length         */
117 :           iobf [ 0 ] ^= 1;
118 :           iocwr ();
119 :           break;
120 :
121 :         case 'n' :                 /* set parity off             */
122 :           iobf [ 0 ] &=~ ( m_PG + m_PE );
123 :           iocwr ();
124 :           break;
125 :
126 :         case 'o' :                 /* set parity odd             */
127 :           iobf [ 0 ] |= m_PG;
128 :           iobf [ 0 ] &=~ m_PE;
129 :           iocwr ();
130 :           break;
131 :
132 :         case 'r' :                 /* toggle error reports       */
133 :           iobf [ 2 ] ^= 1;
134 :           iocwr ();
135 :           break;
136 :
137 :         case 's' :                 /* toggle stop bits           */
138 :           iobf [ 0 ] ^= m_XS;
139 :           iocwr ();
140 :           break;
141 :
142 :         case 'q' :
143 :           fclose ( dvp );
144 :           exit ( 0 );              /* break the loop, get out    */
145 :         }
146 :       cputs ( CLS );               /* clear the display          */
147 :       center ( "CURRENT COMDVR STATUS" );
148 :       report ( u, dvp );           /* report current status      */
149 :     }
150 : }
151 :
152 : center ( s ) char * s;             /* centers a string on CRT    */
153 : { int i ;
154 :   for ( i = 80 - strlen ( s ); i > 0; i -= 2 )
155 :     putch ( ' ' );
156 :   cputs ( s );
157 :   cputs ( "\r\n" );
158 : }
159 :
160 : iocwr ()                           /* IOCTL Write to COMDVR      */
161 : { rvs . x . ax = 0x4403;
162 :   rvs . x . bx = fileno ( dvp );
163 :   rvs . x . cx = 10;
164 :   rvs . x . dx = ( int ) iobf;
165 :   intdos ( & rvs, & rvs );
166 : }
167 :
168 : char * onoff ( x ) int x ;
169 : { return ( x ? " ON" : " OFF" );
170 : }
171 :
172 : report ( unit ) int unit ;
173 : { char temp [ 80 ];
174 :   rvs . x . ax = 0x4402;
175 :   rvs . x . bx = fileno ( dvp );
176 :   rvs . x . cx = 10;
177 :   rvs . x . dx = ( int ) iobf;
178 :   intdos ( & rvs, & rvs );         /* use IOCTL Read to get data */
179 :   sprintf ( temp, "\nDevice ASY%d\t%d BPS, %d-c-%c\r\n\n",
180 :            unit, iobf [ 4 ],           /* baud rate              */
181 :            5 + ( iobf [ 0 ] & m_WL ),  /* word length            */
182 :            ( iobf [ 0 ] & m_PG ?
183 :              ( iobf [ 0 ] & m_PE ? 'E' : 'O' ) : 'N' ),
184 :            ( iobf [ 0 ] & m_XS ? '2' : '1' )); /* stop bits      */
185 :   cputs ( temp );
186 :
187 :   cputs ( "Hardware Interrupts are" );
188 :   cputs ( onoff ( iobf [ 0 ] & HWINT ));
189 :   cputs ( ", Data Terminal Rdy" );
190 :   cputs ( onoff ( iobf [ 0 ] & o_DTR ));
191 :   cputs ( ", Rqst To Send" );
192 :   cputs ( onoff ( iobf [ 0 ] & o_RTS ));
193 :   cputs ( ".\r\n" );
194 :
195 :   cputs ( "Carrier Detect" );
196 :   cputs ( onoff ( iobf [ 1 ] & i_CD ));
197 :   cputs ( ", Data Set Rdy" );
198 :   cputs ( onoff ( iobf [ 1 ] & i_DSR ));
199 :   cputs ( ", Clear to Send" );
200 :   cputs ( onoff ( iobf [ 1 ] & i_CTS ));
201 :   cputs ( ", Ring Indicator" );
202 :   cputs ( onoff ( iobf [ 1 ] & i_RI ));
203 :   cputs ( ".\r\n" );
204 :
205 :   cputs ( l_SRE & iobf [ 1 ] ? "Xmtr SR Empty, " : "" );
206 :   cputs ( l_HRE & iobf [ 1 ] ? "Xmtr HR Empty, " : "" );
207 :   cputs ( l_BRK & iobf [ 1 ] ? "Break Received, " : "" );
208 :   cputs ( l_ER1 & iobf [ 1 ] ? "Framing Error, " : "" );
209 :   cputs ( l_ER2 & iobf [ 1 ] ? "Parity Error, " : "" );
210 :   cputs ( l_ER3 & iobf [ 1 ] ? "Overrun Error, " : "" );
211 :   cputs ( l_RRF & iobf [ 1 ] ? "Rcvr DR Full, " : "" );
212 :   cputs ( "\b\b.\r\n" );
213 :
214 :   cputs ( "Reception errors " );
215 :   if ( iobf [ 2 ] == 1 )
216 :     cputs ( "are encoded as graphics in buffer" );
217 :   else
218 :     cputs ( "set failure flag" );
219 :   cputs ( ".\r\n" );
220 :
221 :   cputs ( "Outgoing Flow Control " );
222 :   if ( iobf [ 3 ] & 4 )
223 :     cputs ( "by XON and XOFF" );
224 :   else
225 :     if ( iobf [ 3 ] & 2 )
226 :       cputs ( "by RTS and CTS" );
227 :   else
228 :     if ( iobf [ 3 ] & 1 )
229 :       cputs ( "by DTR and DSR" );
230 :   else
231 :     cputs ( "disabled" );
232 :   cputs ( ".\r\n" );
233 : }
234 :
235 : /*end of cdvutl.c */



───────────────────────────────────────────────────────────────────────────


Figure 6-5.
CH1.ASM


   1 :         TITLE   CH1.ASM
   2 :
   3 : ; CH1.ASM -- support file for CTERM.C terminal emulator
   4 : ;       set up to work with COM2
   5 : ;       for use with Microsoft C and SMALL model only...
   6 :
   7 : _TEXT   segment byte public 'CODE'
   8 : _TEXT   ends
   9 : _DATA   segment byte public 'DATA'
  10 : _DATA   ends
  11 : CONST   segment byte public 'CONST'
  12 : CONST   ends
  13 : _BSS    segment byte public 'BSS'
  14 : _BSS    ends
  15 :
  16 : DGROUP  GROUP   CONST, _BSS, _DATA
  17 :         assume  cs:_TEXT, DS:DGROUP, ES:DGROUP, SS:DGROUP
  18 :
  19 : _TEXT   segment
  20 :
  21 :         public  _i_m,_rdmdm,_Send_Byte,_wrtmdm,_set_mdm,_u_m
  22 :
  23 : bport   EQU     02F8h           ; COM2 base address, use 03F8H
                                       ; for COM1
  24 : getiv   EQU     350Bh           ; COM2 vectors, use 0CH for COM1
  25 : putiv   EQU     250Bh
  26 : imrmsk  EQU     00001000b       ; COM2 mask, use 00000100b for COM1
  27 : oiv_o   DW      0               ; old int vector save space
  28 : oiv_s   DW      0
  29 :
  30 : bf_pp   DW      in_bf           ; put pointer (last used)
  31 : bf_gp   DW      in_bf           ; get pointer (next to use)
  32 : bf_bg   DW      in_bf           ; start of buffer
  33 : bf_fi   DW      b_last          ; end of buffer
  34 :
  35 : in_bf   DB      512 DUP (?)     ; input buffer
  36 :
  37 : b_last  EQU     $               ; address just past buffer end
  38 :
  39 : bd_dv   DW      0417h           ; baud rate divisors (0=110 bps)
  40 :         DW      0300h           ; code 1 =  150 bps
  41 :         DW      0180h           ; code 2 =  300 bps
  42 :         DW      00C0h           ; code 3 =  600 bps
  43 :         DW      0060h           ; code 4 = 1200 bps
  44 :         DW      0030h           ; code 5 = 2400 bps
  45 :         DW      0018h           ; code 6 = 4800 bps
  46 :         DW      000Ch           ; code 7 = 9600 bps
  47 :
  48 : _set_mdm proc   near            ; replaces BIOS 'init' function
  49 :         PUSH    BP
  50 :         MOV     BP,SP           ; establish stackframe pointer
  51 :         PUSH    ES              ; save registers
  52 :         PUSH    DS
  53 :         MOV     AX,CS           ; point them to CODE segment
  54 :         MOV     DS,AX
  55 :         MOV     ES,AX
  56 :         MOV     AH,[BP+4]       ; get parameter passed by C
  57 :         MOV     DX,BPORT+3      ; point to Line Control Reg
  58 :         MOV     AL,80h          ; set DLAB bit (see text)
  59 :         OUT     DX,AL
  60 :         MOV     DL,AH           ; shift param to BAUD field
  61 :         MOV     CL,4
  62 :         ROL     DL,CL
  63 :         AND     DX,00001110b    ; mask out all other bits
  64 :         MOV     DI,OFFSET bd_dv
  65 :         ADD     DI,DX           ; make pointer to true divisor
  66 :         MOV     DX,BPORT+1      ; set to high byte first
  67 :         MOV     AL,[DI+1]
  68 :         OUT     DX,AL           ; put high byte into UART
  69 :         MOV     DX,BPORT        ; then to low byte
  70 :         MOV     AL,[DI]
  71 :         OUT     DX,AL
  72 :         MOV     AL,AH           ; now use rest of parameter
  73 :         AND     AL,00011111b    ; to set Line Control Reg
  74 :         MOV     DX,BPORT+3
  75 :         OUT     DX,AL
  76 :         MOV     DX,BPORT+2      ; Interrupt Enable Register
  77 :         MOV     AL,1            ; Receive type only
  78 :         OUT     DX,AL
  79 :         POP     DS              ; restore saved registers
  80 :         POP     ES
  81 :         MOV     SP,BP
  82 :         POP     BP
  83 :         RET
  84 : _set_mdm endp
  85 :
  86 : _wrtmdm proc    near            ; write char to modem
  87 : _Send_Byte:                     ; name used by main program
  88 :         PUSH    BP
  89 :         MOV     BP,SP           ; set up pointer and save regs
  90 :         PUSH    ES
  91 :         PUSH    DS
  92 :         MOV     AX,CS
  93 :         MOV     DS,AX
  94 :         MOV     ES,AX
  95 :         MOV     DX,BPORT+4      ; establish DTR, RTS, and OUT2
  96 :         MOV     AL,0Bh
  97 :         OUT     DX,AL
  98 :         MOV     DX,BPORT+6      ; check for on line, CTS
  99 :         MOV     BH,30h
 100 :         CALL    w_tmr
 101 :         JNZ     w_out           ; timed out
 102 :         MOV     DX,BPORT+5      ; check for UART ready
 103 :         MOV     BH,20h
 104 :         CALL    w_tmr
 105 :         JNZ     w_out           ; timed out
 106 :         MOV     DX,BPORT        ; send out to UART port
 107 :         MOV     AL,[BP+4]       ; get char passed from C
 108 :         OUT     DX,AL
 109 : w_out:  POP     DS              ; restore saved regs
 110 :         POP     ES
 111 :         MOV     SP,BP
 112 :         POP     BP
 113 :         RET
 114 : _wrtmdm endp
 115 :
 116 : _rdmdm  proc    near            ; reads byte from buffer
 117 :         PUSH    BP
 118 :         MOV     BP,SP           ; set up ptr, save regs
 119 :         PUSH    ES
 120 :         PUSH    DS
 121 :         MOV     AX,CS
 122 :         MOV     DS,AX
 123 :         MOV     ES,AX
 124 :         MOV     AX,0FFFFh       ; set for EOF flag
 125 :         MOV     BX,bf_gp        ; use "get" ptr
 126 :         CMP     BX,bf_pp        ; compare to "put"
 127 :         JZ      nochr           ; same, empty
 128 :         INC     BX              ; else char available
 129 :         CMP     BX,bf_fi        ; at end of bfr?
 130 :         JNZ     noend           ; no
 131 :         MOV     BX,bf_bg        ; yes, set to beg
 132 : noend:  MOV     AL,[BX]         ; get the char
 133 :         MOV     bf_gp,BX        ; update "get" ptr
 134 :         INC     AH              ; zero AH as flag
 135 : nochr:  POP     DS              ; restore regs
 136 :         POP     ES
 137 :         MOV     SP,BP
 138 :         POP     BP
 139 :         RET
 140 : _rdmdm  endp
 141 :
 142 : w_tmr   proc    near
 143 :         MOV     BL,1            ; wait timer, double loop
 144 : w_tm1:  SUB     CX,CX           ; set up inner loop
 145 : w_tm2:  IN      AL,DX           ; check for requested response
 146 :         MOV     AH,AL           ; save what came in
 147 :         AND     AL,BH           ; mask with desired bits
 148 :         CMP     AL,BH           ; then compare
 149 :         JZ      w_tm3           ; got it, return with ZF set
 150 :         LOOP    w_tm2           ; else keep trying
 151 :         DEC     BL              ; until double loop expires
 152 :         JNZ     w_tm1
 153 :         OR      BH,BH           ; timed out, return NZ
 154 : w_tm3:  RET
 155 : w_tmr:  endp
 156 :
 157 : ; hardware interrupt service routine
 158 : rts_m:  CLI
 159 :         PUSH    DS              ; save all regs
 160 :         PUSH    AX
 161 :         PUSH    BX
 162 :         PUSH    CX
 163 :         PUSH    DX
 164 :         PUSH    CS              ; set DS same as CS
 165 :         POP     DS
 166 :         MOV     DX,BPORT        ; grab the char from UART
 167 :         IN      AL,DX
 168 :         MOV     BX,bf_pp        ; use "put" ptr
 169 :         INC     BX              ; step to next slot
 170 :         CMP     BX,bf_fi        ; past end yet?
 171 :         JNZ     nofix           ; no
 172 :         MOV     BX,bf_bg        ; yes, set to begin
 173 : nofix:  MOV     [BX],AL         ; put char in buffer
 174 :         MOV     bf_pp,BX        ; update "put" ptr
 175 :         MOV     AL,20h          ; send EOI to 8259 chip
 176 :         OUT     20h,AL
 177 :         POP     DX              ; restore regs
 178 :         POP     CX
 179 :         POP     BX
 180 :         POP     AX
 181 :         POP     DS
 182 :         IRET
 183 :
 184 : _i_m    proc    near            ; install modem service
 185 :         PUSH    BP
 186 :         MOV     BP,SP           ; save all regs used
 187 :         PUSH    ES
 188 :         PUSH    DS
 189 :         MOV     AX,CS           ; set DS,ES=CS
 190 :         MOV     DS,AX
 191 :         MOV     ES,AX
 192 :         MOV     DX,BPORT+1      ; Interrupt Enable Reg
 193 :         MOV     AL,0Fh          ; enable all ints now
 194 :         OUT     DX,AL
 195 :
 196 : im1:    MOV     DX,BPORT+2      ; clear junk from UART
 197 :         IN      AL,DX           ; read IID reg of UART
 198 :         MOV     AH,AL           ; save what came in
 199 :         TEST    AL,1            ; anything pending?
 200 :         JNZ     im5             ; no, all clear now
 201 :         CMP     AH,0            ; yes, Modem Status?
 202 :         JNZ     im2             ; no
 203 :         MOV     DX,BPORT+6      ; yes, read MSR to clear
 204 :         IN      AL,DX
 205 : im2:    CMP     AH,2            ; Transmit HR empty?
 206 :         JNZ     im3             ; no (no action needed)
 207 : im3:    CMP     AH,4            ; Received Data Ready?
 208 :         JNZ     im4             ; no
 209 :         MOV     DX,BPORT        ; yes, read it to clear
 210 :         IN      AL,DX
 211 : im4:    CMP     AH,6            ; Line Status?
 212 :         JNZ     im1             ; no, check for more
 213 :         MOV     DX,BPORT+5      ; yes, read LSR to clear
 214 :         IN      AL,DX
 215 :         JMP     im1             ; then check for more
 216 :
 217 : im5:    MOV     DX,BPORT+4      ; set up working conditions
 218 :         MOV     AL,0Bh          ; DTR, RTS, OUT2 bits
 219 :         OUT     DX,AL
 220 :         MOV     AL,1            ; enable RCV interrupt only
 221 :         MOV     DX,BPORT+1
 222 :         OUT     DX,AL
 223 :         MOV     AX,GETIV        ; get old int vector
 224 :         INT     21h
 225 :         MOV     oiv_o,BX        ; save for restoring later
 226 :         MOV     oiv_s,ES
 227 :         MOV     DX,OFFSET rts_m ; set in new one
 228 :         MOV     AX,PUTIV
 229 :         INT     21h
 230 :         IN      AL,21h          ; now enable 8259 PIC
 231 :         AND     AL,NOT IMRMSK
 232 :         OUT     21h,AL
 233 :         MOV     AL,20h          ; then send out an EOI
 234 :         OUT     20h,AL
 235 :         POP     DS              ; restore
 236 :         POP     ES
 237 :         MOV     SP,BP
 238 :         POP     BP
 239 :         RET
 240 : _i_m    endp
 241 :
 242 : _u_m    proc    near            ; uninstall modem service
 243 :         PUSH    BP
 244 :         MOV     BP,SP           ; save registers
 245 :         IN      AL,21h          ; disable COM int in 8259
 246 :         OR      AL,IMRMSK
 247 :         OUT     21h,AL
 248 :         PUSH    ES
 249 :         PUSH    DS
 250 :         MOV     AX,CS           ; set same as CS
 251 :         MOV     DS,AX
 252 :         MOV     ES,AX
 253 :         MOV     AL,0            ; disable UART ints
 254 :         MOV     DX,BPORT+1
 255 :         OUT     DX,AL
 256 :         MOV     DX,oiv_o        ; restore original vector
 257 :         MOV     DS,oiv_s
 258 :         MOV     AX,PUTIV
 259 :         INT     21h
 260 :         POP     DS              ; restore registers
 261 :         POP     ES
 262 :         MOV     SP,BP
 263 :         POP     BP
 264 :         RET
 265 : _u_m    endp
 266 :
 267 : _TEXT   ends
 268 :
 269 :         END



───────────────────────────────────────────────────────────────────────────


Figure 6-6.
CH1A.ASM.


   1 :         TITLE   CH1A.ASM
   2 :
   3 : ; CH1A.ASM -- support file for CTERM.C terminal emulator
   4 : ;       this set of routines replaces Ctrl-C/Ctrl-BREAK
   5 : ;       usage: void set_int(), rst_int();
   6 : ;               int broke();    /* boolean if BREAK     */
   7 : ;       for use with Microsoft C and SMALL model only...
   8 :
   9 : _TEXT   segment byte public 'CODE'
  10 : _TEXT   ends
  11 : _DATA   segment byte public 'DATA'
  12 : _DATA   ends
  13 : CONST   segment byte public 'CONST'
  14 : CONST   ends
  15 : _BSS    segment byte public 'BSS'
  16 : _BSS    ends
  17 :
  18 : DGROUP  GROUP   CONST, _BSS, _DATA
  19 :         ASSUME  CS:_TEXT, DS:DGROUP, ES:DGROUP, SS:DGROUP
  20 :
  21 : _DATA   SEGMENT BYTE PUBLIC 'DATA'
  22 :
  23 : OLDINT1B DD     0               ; storage for original INT 1BH
                                       ; vector
  24 :
  25 : _DATA   ENDS
  26 :
  27 : _TEXT   SEGMENT
  28 :
  29 :         PUBLIC  _set_int,_rst_int,_broke
  30 :
  31 : myint1b:
  32 :         mov     word ptr cs:brkflg,1Bh          ; make it nonzero
  33 :         iret
  34 :
  35 : myint23:
  36 :         mov     word ptr cs:brkflg,23h          ; make it nonzero
  37 :         iret
  38 :
  39 : brkflg  dw      0               ; flag that BREAK occurred
  40 :
  41 : _broke  proc    near            ; returns 0 if no break
  42 :         xor     ax,ax           ; prepare to reset flag
  43 :         xchg    ax,cs:brkflg    ; return current flag value
  44 :         ret
  45 : _broke  endp
  46 :
  47 : _set_int proc near
  48 :         mov     ax,351bh        ; get interrupt vector for 1BH
  49 :         int     21h             ; (don't need to save for 23H)
  50 :         mov     word ptr oldint1b,bx     ; save offset in first word
  51 :         mov     word ptr oldint1b+2,es   ; save segment in second
                                                ; word
  52 :
  53 :         push    ds              ; save our data segment
  54 :         mov     ax,cs           ; set DS to CS for now
  55 :         mov     ds,ax
  56 :         lea     dx,myint1b      ; DS:DX points to new routine
  57 :         mov     ax,251bh        ; set interrupt vector
  58 :         int     21h
  59 :         mov     ax,cs           ; set DS to CS for now
  60 :         mov     ds,ax
  61 :         lea     dx,myint23      ; DS:DX points to new routine
  62 :         mov     ax,2523h        ; set interrupt vector
  63 :         int     21h
  64 :         pop     ds              ; restore data segment
  65 :         ret
  66 : _set_int endp
  67 :
  68 : _rst_int proc near
  69 :         push    ds              ; save our data segment
  70 :         lds     dx,oldint1b     ; DS:DX points to original
  71 :         mov     ax,251bh        ; set interrupt vector
  72 :         int     21h
  73 :         pop     ds              ; restore data segment
  74 :         ret
  75 : _rst_int endp
  76 :
  77 : _TEXT   ends
  78 :
  79 :         END



───────────────────────────────────────────────────────────────────────────


Figure 6-7.
CH2.ASM.


   1 :         TITLE   CH2.ASM
   2 :
   3 : ; CH2.ASM -- support file for CTERM.C terminal emulator
   4 : ;       for use with Microsoft C and SMALL model only...
   5 :
   6 : _TEXT   segment byte public 'CODE'
   7 : _TEXT   ends
   8 : _DATA   segment byte public 'DATA'
   9 : _DATA   ends
  10 : CONST   segment byte public 'CONST'
  11 : CONST   ends
  12 : _BSS    segment byte public 'BSS'
  13 : _BSS    ends
  14 :
  15 : DGROUP  GROUP   CONST, _BSS, _DATA
  16 :         assume  CS:_TEXT, DS:DGROUP, ES:DGROUP, SS:DGROUP
  17 :
  18 : _TEXT   segment
  19 :
  20 :         public  __cls,__color,__deol,__i_v,__key,__wrchr,__wrpos
  21 :
  22 : atrib   DB      0               ; attribute
  23 : _colr   DB      0               ; color
  24 : v_bas   DW      0               ; video segment
  25 : v_ulc   DW      0               ; upper left corner cursor
  26 : v_lrc   DW      184Fh           ; lower right corner cursor
  27 : v_col   DW      0               ; current col/row
  28 :
  29 : __key   proc    near            ; get keystroke
  30 :         PUSH    BP
  31 :         MOV     AH,1            ; check status via BIOS
  32 :         INT     16h
  33 :         MOV     AX,0FFFFh
  34 :         JZ      key00           ; none ready, return EOF
  35 :         MOV     AH,0            ; have one, read via BIOS
  36 :         INT     16h
  37 : key00:  POP     BP
  38 :         RET
  39 : __key   endp
  40 :
  41 : __wrchr proc    near
  42 :         PUSH    BP
  43 :         MOV     BP,SP
  44 :         MOV     AL,[BP+4]       ; get char passed by C
  45 :         CMP     AL,' '
  46 :         JNB     prchr           ; printing char, go do it
  47 :         CMP     AL,8
  48 :         JNZ     notbs
  49 :         DEC     BYTE PTR v_col  ; process backspace
  50 :         MOV     AL,byte ptr v_col
  51 :         CMP     AL,byte ptr v_ulc
  52 :         JB      nxt_c           ; step to next column
  53 :         JMP     norml
  54 :
  55 : notbs:  CMP     AL,9
  56 :         JNZ     notht
  57 :         MOV     AL,byte ptr v_col       ; process HTAB
  58 :         ADD     AL,8
  59 :         AND     AL,0F8h
  60 :         MOV     byte ptr v_col,AL
  61 :         CMP     AL,byte ptr v_lrc
  62 :         JA      nxt_c
  63 :         JMP     SHORT   norml
  64 :
  65 : notht:  CMP     AL,0Ah
  66 :         JNZ     notlf
  67 :         MOV     AL,byte ptr v_col+1     ; process linefeed
  68 :         INC     AL
  69 :         CMP     AL,byte ptr v_lrc+1
  70 :         JBE     noht1
  71 :         CALL    scrol
  72 :         MOV     AL,byte ptr v_lrc+1
  73 : noht1:  MOV     byte ptr v_col+1,AL
  74 :         JMP     SHORT   norml
  75 :
  76 : notlf:  CMP     AL,0Ch
  77 :         JNZ     ck_cr
  78 :         CALL    __cls           ; process formfeed
  79 :         JMP     SHORT   ignor
  80 :
  81 : ck_cr:  CMP     AL,0Dh
  82 :         JNZ     ignor           ; ignore all other CTL chars
  83 :         MOV     AL,byte ptr v_ulc       ; process CR
  84 :         MOV     byte ptr v_col,AL
  85 :         JMP     SHORT   norml
  86 :
  87 : prchr:  MOV     AH,_colr        ; process printing char
  88 :         PUSH    AX
  89 :         XOR     AH,AH
  90 :         MOV     AL,byte ptr v_col+1
  91 :         PUSH    AX
  92 :         MOV     AL,byte ptr v_col
  93 :         PUSH    AX
  94 :         CALL    wrtvr
  95 :         MOV     SP,BP
  96 : nxt_c:  INC     BYTE PTR v_col  ; advance to next column
  97 :         MOV     AL,byte ptr v_col
  98 :         CMP     AL,byte ptr v_lrc
  99 :         JLE     norml
 100 :         MOV     AL,0Dh          ; went off end, do CR/LF
 101 :         PUSH    AX
 102 :         CALL    __wrchr
 103 :         POP     AX
 104 :         MOV     AL,0Ah
 105 :         PUSH    AX
 106 :         CALL    __wrchr
 107 :         POP     AX
 108 : norml:  CALL    set_cur
 109 : ignor:  MOV     SP,BP
 110 :         POP     BP
 111 :         RET
 112 : __wrchr endp
 113 :
 114 : __i_v   proc    near            ; establish video base segment
 115 :         PUSH    BP
 116 :         MOV     BP,SP
 117 :         MOV     AX,0B000h       ; mono, B800 for CGA
 118 :         MOV     v_bas,AX        ; could be made automatic
 119 :         MOV     SP,BP
 120 :         POP     BP
 121 :         RET
 122 : __i_v   endp
 123 :
 124 : __wrpos proc    near            ; set cursor position
 125 :         PUSH    BP
 126 :         MOV     BP,SP
 127 :         MOV     DH,[BP+4]       ; row from C program
 128 :         MOV     DL,[BP+6]       ; col from C program
 129 :         MOV     v_col,DX        ; cursor position
 130 :         MOV     BH,atrib        ; attribute
 131 :         MOV     AH,2
 132 :         PUSH    BP
 133 :         INT     10h
 134 :         POP     BP
 135 :         MOV     AX,v_col        ; return cursor position
 136 :         MOV     SP,BP
 137 :         POP     BP
 138 :         RET
 139 : __wrpos endp
 140 :
 141 : set_cur proc    near            ; set cursor to v_col
 142 :         PUSH    BP
 143 :         MOV     BP,SP
 144 :         MOV     DX,v_col        ; use where v_col says
 145 :         MOV     BH,atrib
 146 :         MOV     AH,2
 147 :         PUSH    BP
 148 :         INT     10h
 149 :         POP     BP
 150 :         MOV     AX,v_col
 151 :         MOV     SP,BP
 152 :         POP     BP
 153 :         RET
 154 : set_cur endp
 155 :
 156 : __color proc    near            ; _color(fg, bg)
 157 :         PUSH    BP
 158 :         MOV     BP,SP
 159 :         MOV     AH,[BP+6]       ; background from C
 160 :         MOV     AL,[BP+4]       ; foreground from C
 161 :         MOV     CX,4
 162 :         SHL     AH,CL
 163 :         AND     AL,0Fh
 164 :         OR      AL,AH           ; pack up into 1 byte
 165 :         MOV     _colr,AL        ; store for handler's use
 166 :         XOR     AH,AH
 167 :         MOV     SP,BP
 168 :         POP     BP
 169 :         RET
 170 : __color endp
 171 :
 172 : scrol   proc    near            ; scroll CRT up by one line
 173 :         PUSH    BP
 174 :         MOV     BP,SP
 175 :         MOV     AL,1            ; count of lines to scroll
 176 :         MOV     CX,v_ulc
 177 :         MOV     DX,v_lrc
 178 :         MOV     BH,_colr
 179 :         MOV     AH,6
 180 :         PUSH    BP
 181 :         INT     10h             ; use BIOS
 182 :         POP     BP
 183 :         MOV     SP,BP
 184 :         POP     BP
 185 :         RET
 186 : scrol   endp
 187 :
 188 : __cls   proc    near            ; clear CRT
 189 :         PUSH    BP
 190 :         MOV     BP,SP
 191 :         MOV     AL,0            ; flags CLS to BIOS
 192 :         MOV     CX,v_ulc
 193 :         MOV     v_col,CX        ; set to HOME
 194 :         MOV     DX,v_lrc
 195 :         MOV     BH,_colr
 196 :         MOV     AH,6
 197 :         PUSH    BP
 198 :         INT     10h             ; use BIOS scroll up
 199 :         POP     BP
 200 :         CALL    set_cur         ; cursor to HOME
 201 :         MOV     SP,BP
 202 :         POP     BP
 203 :         RET
 204 : __cls   endp
 205 :
 206 : __deol  proc    near            ; delete to end of line
 207 :         PUSH    BP
 208 :         MOV     BP,SP
 209 :         MOV     AL,' '
 210 :         MOV     AH,_colr        ; set up blanks
 211 :         PUSH    AX
 212 :         MOV     AL,byte ptr v_col+1
 213 :         XOR     AH,AH           ; set up row value
 214 :         PUSH    AX
 215 :         MOV     AL,byte ptr v_col
 216 :
 217 : deol1:  CMP     AL,byte ptr v_lrc
 218 :         JA      deol2           ; at RH edge
 219 :         PUSH    AX              ; current location
 220 :         CALL    wrtvr           ; write a blank
 221 :         POP     AX
 222 :         INC     AL              ; next column
 223 :         JMP     deol1           ; do it again
 224 :
 225 : deol2:  MOV     AX,v_col        ; return cursor position
 226 :         MOV     SP,BP
 227 :         POP     BP
 228 :         RET
 229 : __deol  endp
 230 :
 231 : wrtvr   proc    near            ; write video RAM (col, row,
                                       ; char/atr)
 232 :         PUSH    BP
 233 :         MOV     BP,SP           ; set up arg ptr
 234 :         MOV     DL,[BP+4]       ; column
 235 :         MOV     DH,[BP+6]       ; row
 236 :         MOV     BX,[BP+8]       ; char/atr
 237 :         MOV     AL,80           ; calc offset
 238 :         MUL     DH
 239 :         XOR     DH,DH
 240 :         ADD     AX,DX
 241 :         ADD     AX,AX           ; adjust bytes to words
 242 :         PUSH    ES              ; save seg reg
 243 :         MOV     DI,AX
 244 :         MOV     AX,v_bas        ; set up segment
 245 :         MOV     ES,AX
 246 :         MOV     AX,BX           ; get the data
 247 :         STOSW                   ; put on screen
 248 :         POP     ES              ; restore regs
 249 :         MOV     SP,BP
 250 :         POP     BP
 251 :         RET
 252 : wrtvr   endp
 253 :
 254 : _TEXT   ends
 255 :
 256 :         END



───────────────────────────────────────────────────────────────────────────


Figure 6-8.
CTERM.C.


   1 : /* Terminal Emulator    (cterm.c)
   2 :  *      Jim Kyle, 1987
   3 :  *
   4 :  *      Uses files CH1, CH1A, and CH2 for MASM support...
   5 :  */
   6 :
   7 : #include <stdio.h>
   8 : #include <conio.h>                      /* special console i/o    */
   9 : #include <stdlib.h>                     /* misc definitions       */
  10 : #include <dos.h>                        /* defines intdos()       */
  11 : #include <string.h>
  12 : #define BRK  'C'-'@'                    /* control characters     */
  13 : #define ESC  '['-'@'
  14 : #define XON  'Q'-'@'
  15 : #define XOFF 'S'-'@'
  16 :
  17 : #define True  1
  18 : #define False  0
  19 :
  20 : #define Is_Function_Key(C) ( (C) == ESC )
  21 :
  22 : static char capbfr [ 4096 ];            /* capture buffer         */
  23 : static int wh,
  24 :      ws;
  25 :
  26 : static int I,
  27 :     waitchr = 0,
  28 :     vflag = False,
  29 :     capbp,
  30 :     capbc,
  31 :     Ch,
  32 :     Want_7_Bit = True,
  33 :     ESC_Seq_State = 0;         /* escape sequence state variable   */
  34 :
  35 : int _cx ,
  36 :      _cy,
  37 :      _atr = 0x07,                       /* white on black         */
  38 :      _pag = 0,
  39 :      oldtop = 0,
  40 :      oldbot = 0x184f;
  41 :
  42 : FILE * in_file = NULL;         /* start with keyboard input       */
  43 : FILE * cap_file = NULL;
  44 :
  45 : #include "cterm.h"             /* external declarations, etc.     */
  46 :
  47 : int Wants_To_Abort ()          /* checks for interrupt of script  */
  48 : { return broke ();
  49 : }
  50 : void
  51 :
  52 : main ( argc, argv ) int argc ; /* main routine                    */
  53 :  char * argv [];
  54 : { char * cp,
  55 :      * addext ();
  56 :   if ( argc > 1 )                /* check for script filename     */
  57 :     in_file = fopen ( addext ( argv [ 1 ], ".SCR" ), "r" );
  58 :   if ( argc > 2 )                /* check for capture filename    */
  59 :     cap_file = fopen ( addext ( argv [ 2 ], ".CAP" ), "w" );
  60 :   set_int ();                    /* install CH1 module            */
  61 :   Set_Vid ();                    /* get video setup               */
  62 :   cls ();                        /* clear the screen              */
  63 :   cputs ( "Terminal Emulator" ); /* tell who's working            */
  64 :   cputs ( "\r\n< ESC for local commands >\r\n\n" );
  65 :   Want_7_Bit = True;
  66 :   ESC_Seq_State = 0;
  67 :   Init_Comm ();                  /* set up drivers, etc.          */
  68 :   while ( 1 )                           /* main loop              */
  69 :     { if (( Ch = kb_file ()) > 0 )      /* check local            */
  70 :         { if ( Is_Function_Key ( Ch ))
  71 :             { if ( docmd () < 0 )       /* command                */
  72 :                 break;
  73 :             }
  74 :           else
  75 :             Send_Byte ( Ch & 0x7F );    /* else send it           */
  76 :         }
  77 :       if (( Ch = Read_Modem ()) >= 0 )  /* check remote           */
  78 :         { if ( Want_7_Bit )
  79 :             Ch &= 0x7F;                 /* trim off high bit      */
  80 :           switch ( ESC_Seq_State )      /* state machine          */
  81 :             {
  82 :             case 0 :                    /* no Esc sequence        */
  83 :               switch ( Ch )
  84 :                 {
  85 :                 case ESC  :             /* Esc char received      */
  86 :                   ESC_Seq_State = 1;
  87 :                   break;
  88 :
  89 :                 default :
  90 :                   if ( Ch == waitchr )  /* wait if required       */
  91 :                     waitchr = 0;
  92 :                   if ( Ch == 12 )       /* clear screen on FF     */
  93 :                     cls ();
  94 :                   else
  95 :                     if ( Ch != 127 )    /* ignore rubouts         */
  96 :                       { putchx ( (char) Ch );  /* handle all      */
                                                      /* others          */
  97 :                         put_cap ( (char) Ch );
  98 :                       }
  99 :                 }
 100 :               break;
 101 :
 102 :             case 1 : /* ESC -- process any escape sequences here  */
 103 :               switch ( Ch )
 104 :                 {
 105 :                 case 'A' :              /* VT52 up                */
 106 :                   ;                     /* nothing but stubs here */
 107 :                   ESC_Seq_State = 0;
 108 :                   break;
 109 :
 110 :                 case 'B' :              /* VT52 down              */
 111 :                   ;
 112 :                   ESC_Seq_State = 0;
 113 :                   break;
 114 :
 115 :                 case 'C' :              /* VT52 left              */
 116 :                   ;
 117 :                   ESC_Seq_State = 0;
 118 :                   break;
 119 :
 120 :                 case 'D' :              /* VT52 right             */
 121 :                   ;
 122 :                   ESC_Seq_State = 0;
 123 :                   break;
 124 :
 125 :                 case 'E' :              /* VT52 Erase CRT         */
 126 :                   cls ();               /* actually do this one   */
 127 :                   ESC_Seq_State = 0;
 128 :                   break;
 129 :
 130 :                 case 'H' :              /* VT52 home cursor       */
 131 :                   locate ( 0, 0 );
 132 :                   ESC_Seq_State = 0;
 133 :                   break;
 134 :
 135 :                 case 'j' :              /* VT52 Erase to EOS      */
 136 :                   deos ();
 137 :                   ESC_Seq_State = 0;
 138 :                   break;
 139 :
 140 :                 case '[' :      /* ANSI.SYS - VT100 sequence      */
 141 :                   ESC_Seq_State = 2;
 142 :                   break;
 143 :
 144 :                 default :
 145 :                   putchx ( ESC );       /* pass thru all others   */
 146 :                   putchx ( (char) Ch );
 147 :                   ESC_Seq_State = 0;
 148 :                 }
 149 :               break;
 150 :
 151 :             case 2 :                    /* ANSI 3.64 decoder      */
 152 :               ESC_Seq_State = 0;        /* not implemented        */
 153 :             }
 154 :         }
 155 :       if ( broke ())                    /* check CH1A handlers    */
 156 :         { cputs ( "\r\n***BREAK***\r\n" );
 157 :           break;
 158 :         }
 159 :     }                                   /* end of main loop       */
 160 :   if ( cap_file )                       /* save any capture       */
 161 :     cap_flush ();
 162 :   Term_Comm ();                         /* restore when done      */
 163 :   rst_int ();                           /* restore break handlers */
 164 :   exit ( 0 );                           /* be nice to MS-DOS      */
 165 : }
 166 :
 167 : docmd ()                                /* local command shell    */
 168 : { FILE * getfil ();
 169 :   int wp;
 170 :   wp = True;
 171 :   if ( ! in_file || vflag )
 172 :     cputs ( "\r\n\tCommand: " );        /* ask for command        */
 173 :   else
 174 :     wp = False;
 175 :   Ch = toupper ( kbd_wait ());          /* get response           */
 176 :   if ( wp )
 177 :     putchx ( (char) Ch );
 178 :   switch  ( Ch )                        /* and act on it          */
 179 :     {
 180 :     case 'S' :
 181 :       if ( wp )
 182 :         cputs ( "low speed\r\n" );
 183 :       Set_Baud ( 300 );
 184 :       break;
 185 :
 186 :     case 'D' :
 187 :       if ( wp )
 188 :         cputs ( "elay (1-9 sec): " );
 189 :       Ch = kbd_wait ();
 190 :       if ( wp )
 191 :         putchx ( (char) Ch );
 192 :       Delay ( 1000 * ( Ch - '0' ));
 193 :       if ( wp )
 194 :         putchx ( '\n' );
 195 :       break;
 196 :
 197 :     case 'E' :
 198 :       if ( wp )
 199 :         cputs ( "ven Parity\r\n" );
 200 :       Set_Parity ( 2 );
 201 :       break;
 202 :
 203 :     case 'F' :
 204 :       if ( wp )
 205 :         cputs ( "ast speed\r\n" );
 206 :       Set_Baud ( 1200 );
 207 :       break;
 208 :
 209 :     case 'H' :
 210 :       if ( wp )
 211 :         { cputs ( "\r\n\tVALID COMMANDS:\r\n" );
 212 :           cputs ( "\tD = delay 0-9 seconds.\r\n" );
 213 :           cputs ( "\tE = even parity.\r\n" );
 214 :           cputs ( "\tF = (fast) 1200-baud.\r\n" );
 215 :           cputs ( "\tN = no parity.\r\n" );
 216 :           cputs ( "\tO = odd parity.\r\n" );
 217 :           cputs ( "\tQ = quit, return to DOS.\r\n" );
 218 :           cputs ( "\tR = reset modem.\r\n" );
 219 :           cputs ( "\tS = (slow) 300-baud.\r\n" );
 220 :           cputs ( "\tU = use script file.\r\n" );
 221 :           cputs ( "\tV = verify file input.\r\n" );
 222 :           cputs ( "\tW = wait for char." );
 223 :         }
 224 :       break;
 225 :
 226 :     case 'N' :
 227 :       if ( wp )
 228 :         cputs ( "o Parity\r\n" );
 229 :       Set_Parity ( 1 );
 230 :       break;
 231 :
 232 :     case 'O' :
 233 :       if ( wp )
 234 :         cputs ( "dd Parity\r\n" );
 235 :       Set_Parity ( 3 );
 236 :       break;
 237 :
 238 :     case 'R' :
 239 :       if ( wp )
 240 :         cputs ( "ESET Comm Port\r\n" );
 241 :       Init_Comm ();
 242 :       break;
 243 :
 244 :     case 'Q' :
 245 :       if ( wp )
 246 :         cputs ( " = QUIT Command\r\n" );
 247 :       Ch = ( - 1 );
 248 :       break;
 249 :
 250 :     case 'U' :
 251 :       if ( in_file && ! vflag )
 252 :         putchx ( 'U' );
 253 :       cputs ( "se file: " );
 254 :       getfil ();
 255 :       cputs ( "File " );
 256 :       cputs ( in_file ? "Open\r\n" : "Bad\r\n" );
 257 :       waitchr = 0;
 258 :       break;
 259 :
 260 :     case 'V' :
 261 :       if ( wp )
 262 :         { cputs ( "erify flag toggled " );
 263 :           cputs ( vflag ? "OFF\r\n" : "ON\r\n" );
 264 :         }
 265 :       vflag = vflag ? False : True;
 266 :       break;
 267 :
 268 :     case 'W' :
 269 :       if ( wp )
 270 :         cputs ( "ait for: <" );
 271 :       waitchr = kbd_wait ();
 272 :       if ( waitchr == ' ' )
 273 :         waitchr = 0;
 274 :       if ( wp )
 275 :         { if ( waitchr )
 276 :             putchx ( (char) waitchr );
 277 :           else
 278 :             cputs ( "no wait" );
 279 :           cputs ( ">\r\n" );
 280 :         }
 281 :       break;
 282 :
 283 :     default :
 284 :       if ( wp )
 285 :         { cputs ( "Don't know " );
 286 :           putchx ( (char) Ch );
 287 :           cputs ( "\r\nUse 'H' command for Help.\r\n" );
 288 :         }
 289 :       Ch = '?';
 290 :     }
 291 :   if ( wp )                           /* if window open...        */
 292 :     { cputs ( "\r\n[any key]\r" );
 293 :       while ( Read_Keyboard () == EOF ) /* wait for response      */
 294 :         ;
 295 :     }
 296 :   return Ch ;
 297 : }
 298 :
 299 : kbd_wait ()                           /* wait for input           */
 300 : { int c ;
 301 :   while (( c = kb_file ()) == ( - 1 ))
 302 :     ;
 303 :   return c & 255;
 304 : }
 305 :
 306 : kb_file ()                            /* input from kb or file    */
 307 : { int c ;
 308 :   if ( in_file )                      /* USING SCRIPT             */
 309 :     { c = Wants_To_Abort ();          /* use first as flag        */
 310 :       if ( waitchr && ! c )
 311 :         c = ( - 1 );                  /* then for char            */
 312 :       else
 313 :         if ( c || ( c = getc ( in_file )) == EOF || c == 26 )
 314 :           { fclose ( in_file );
 315 :             cputs ( "\r\nScript File Closed\r\n" );
 316 :             in_file = NULL;
 317 :             waitchr = 0;
 318 :             c = ( - 1 );
 319 :           }
 320 :       else
 321 :         if ( c == '\n' )              /* ignore LFs in file       */
 322 :           c = ( - 1 );
 323 :       if ( c == '\\' )                /* process Esc sequence     */
 324 :         c = esc ();
 325 :       if ( vflag && c != ( - 1 ))     /* verify file char         */
 326 :         { putchx ( '{' );
 327 :           putchx ( (char) c );
 328 :           putchx ( '}' );
 329 :         }
 330 :     }
 331 :   else                                /* USING CONSOLE            */
 332 :     c = Read_Keyboard ();             /* if not using file        */
 333 :   return ( c );
 334 : }
 335 :
 336 : esc ()                                /* script translator        */
 337 : { int c ;
 338 :   c = getc ( in_file );               /* control chars in file    */
 339 :   switch ( toupper ( c ))
 340 :     {
 341 :     case 'E' :
 342 :       c = ESC;
 343 :       break;
 344 :
 345 :     case 'N' :
 346 :       c = '\n';
 347 :       break;
 348 :
 349 :     case 'R' :
 350 :       c = '\r';
 351 :       break;
 352 :
 353 :     case 'T' :
 354 :       c = '\t';
 355 :       break;
 356 :
 357 :     case '^' :
 358 :       c = getc ( in_file ) & 31;
 359 :       break;
 360 :     }
 361 :   return ( c );
 362 : }
 363 :
 364 : FILE * getfil ()
 365 : { char fnm [ 20 ];
 366 :   getnam ( fnm, 15 );                 /* get the name             */
 367 :   if ( ! ( strchr ( fnm, '.' )))
 368 :     strcat ( fnm, ".SCR" );
 369 :   return ( in_file = fopen ( fnm, "r" ));
 370 : }
 371 :
 372 : void getnam ( b, s ) char * b;        /* take input to buffer     */
 373 :  int s ;
 374 : { while ( s -- > 0 )
 375 :     { if (( * b = (char) kbd_wait ()) != '\r' )
 376 :         putchx ( * b ++ );
 377 :       else
 378 :         break ;
 379 :     }
 380 :   putchx ( '\n' );
 381 :   * b = 0;
 382 : }
 383 :
 384 : char * addext ( b,                    /* add default EXTension    */
 385 :      e ) char * b,
 386 :      * e;
 387 : { static char bfr [ 20 ];
 388 :   if ( strchr ( b, '.' ))
 389 :     return ( b );
 390 :   strcpy ( bfr, b );
 391 :   strcat ( bfr, e );
 392 :   return ( bfr );
 393 : }
 394 :
 395 : void put_cap ( c ) char c ;
 396 : { if ( cap_file && c != 13 )          /* strip out CRs            */
 397 :     fputc ( c, cap_file );            /* use MS-DOS buffering     */
 398 : }
 399 :
 400 : void cap_flush ()                     /* end Capture mode         */
 401 : { if ( cap_file )
 402 :     { fclose ( cap_file );
 403 :       cap_file = NULL;
 404 :       cputs ( "\r\nCapture file closed\r\n" );
 405 :     }
 406 : }
 407 :
 408 : /*      TIMER SUPPORT STUFF (IBMPC/MSDOS)       */
 409 : static long timr;                     /* timeout register         */
 410 :
 411 : static union REGS rgv ;
 412 :
 413 : long getmr ()
 414 : { long now ;                          /* msec since midnite       */
 415 :   rgv.x.ax = 0x2c00;
 416 :   intdos ( & rgv, & rgv );
 417 :   now = rgv.h.ch;                     /* hours                    */
 418 :   now *= 60L;                         /* to minutes               */
 419 :   now += rgv.h.cl;                    /* plus min                 */
 420 :   now *= 60L;                         /* to seconds               */
 421 :   now += rgv.h.dh;                    /* plus sec                 */
 422 :   now *= 100L;                        /* to 1/100                 */
 423 :   now += rgv.h.dl;                    /* plus 1/100               */
 424 :   return ( 10L * now );               /* msec value               */
 425 : }
 426 :
 427 : void Delay ( n ) int n ;              /* sleep for n msec         */
 428 : { long wakeup ;
 429 :   wakeup = getmr () + ( long ) n;     /* wakeup time              */
 430 :   while ( getmr () < wakeup )
 431 :     ;                                 /* now sleep                */
 432 : }
 433 :
 434 : void Start_Timer ( n ) int n ;       /* set timeout for n sec     */
 435 : { timr = getmr () + ( long ) n * 1000L;
 436 : }
 437 :
 438 : Timer_Expired ()      /* if timeout return 1 else return 0        */
 439 : { return ( getmr () > timr );
 440 : }
 441 :
 442 : Set_Vid ()
 443 : { _i_v ();                            /* initialize video         */
 444 :   return 0;
 445 : }
 446 :
 447 : void locate ( row, col ) int row ,
 448 :      col;
 449 : { _cy = row % 25;
 450 :   _cx = col % 80;
 451 :   _wrpos ( row, col );                /* use ML from CH2.ASM      */
 452 : }
 453 :
 454 : void deol ()
 455 : { _deol ();                           /* use ML from CH2.ASM      */
 456 : }
 457 :
 458 : void deos ()
 459 : { deol ();
 460 :   if ( _cy < 24 )                     /* if not last, clear       */
 461 :     { rgv.x.ax = 0x0600;
 462 :       rgv.x.bx = ( _atr << 8 );
 463 :       rgv.x.cx = ( _cy + 1 ) << 8;
 464 :       rgv.x.dx = 0x184F;
 465 :       int86 ( 0x10, & rgv, & rgv );
 466 :     }
 467 :   locate ( _cy, _cx );
 468 : }
 469 :
 470 : void cls ()
 471 : { _cls ();                            /* use ML                   */
 472 : }
 473 :
 474 : void cursor ( yn ) int yn ;
 475 : { rgv.x.cx = yn ? 0x0607 : 0x2607;    /* ON/OFF                   */
 476 :   rgv.x.ax = 0x0100;
 477 :   int86 ( 0x10, & rgv, & rgv );
 478 : }
 479 :
 480 : void revvid ( yn ) int yn ;
 481 : { if ( yn )
 482 :     _atr = _color ( 8, 7 );           /* black on white           */
 483 :   else
 484 :     _atr = _color ( 15, 0 );          /* white on black           */
 485 : }
 486 :
 487 : putchx ( c ) char c ;                 /* put char to CRT          */
 488 : { if ( c == '\n' )
 489 :     putch ( '\r' );
 490 :   putch ( c );
 491 :   return c ;
 492 : }
 493 :
 494 : Read_Keyboard ()                  /* get keyboard character       */
 495 :                                      returns -1 if none present   */
 496 : { int c ;
 497 :   if ( kbhit ())                      /* no char at all           */
 498 :     return ( getch ());
 499 :   return ( EOF );
 500 : }
 501 :
 502 : /*      MODEM SUPPORT                 */
 503 : static char mparm,
 504 :      wrk [ 80 ];
 505 :
 506 : void Init_Comm ()             /* initialize comm port stuff       */
 507 : { static int ft = 0;                  /* firstime flag            */
 508 :   if ( ft ++ == 0 )
 509 :     i_m ();
 510 :   Set_Parity ( 1 );                   /* 8,N,1                    */
 511 :   Set_Baud ( 1200 );                  /* 1200 baud                */
 512 : }
 513 :
 514 : #define B1200 0x80                    /* baudrate codes           */
 515 : #define B300 0x40
 516 :
 517 : Set_Baud ( n ) int n ;                /* n is baud rate           */
 518 : { if ( n == 300 )
 519 :     mparm = ( mparm & 0x1F ) + B300;
 520 :   else
 521 :     if ( n == 1200 )
 522 :       mparm = ( mparm & 0x1F ) + B1200;
 523 :   else
 524 :     return 0;                         /* invalid speed            */
 525 :   sprintf ( wrk, "Baud rate = %d\r\n", n );
 526 :   cputs ( wrk );
 527 :   set_mdm ( mparm );
 528 :   return n ;
 529 : }
 530 :
 531 : #define PAREVN 0x18                   /* MCR bits for commands    */
 532 : #define PARODD 0x10
 533 : #define PAROFF 0x00
 534 : #define STOP2 0x40
 535 : #define WORD8 0x03
 536 : #define WORD7 0x02
 537 : #define WORD6 0x01
 538 :
 539 : Set_Parity ( n ) int n ;              /* n is parity code         */
 540 : { static int mmode;
 541 :   if ( n == 1 )
 542 :     mmode = ( WORD8 | PAROFF );        /* off                     */
 543 :   else
 544 :     if ( n == 2 )
 545 :       mmode = ( WORD7 | PAREVN );      /* on and even             */
 546 :   else
 547 :     if ( n == 3 )
 548 :       mmode = ( WORD7 | PARODD );      /* on and odd              */
 549 :   else
 550 :     return 0;                         /* invalid code             */
 551 :   mparm = ( mparm & 0xE0 ) + mmode;
 552 :   sprintf ( wrk, "Parity is %s\r\n", ( n == 1 ? "OFF" :
 553 :                                      ( n == 2 ? "EVEN" : "ODD" )));
 554 :   cputs ( wrk );
 555 :   set_mdm ( mparm );
 556 :   return n ;
 557 : }
 558 :
 559 : Write_Modem ( c ) char c ;            /* return 1 if ok, else 0   */
 560 : { wrtmdm ( c );
 561 :   return ( 1 );                       /* never any error          */
 562 : }
 563 :
 564 : Read_Modem ()
 565 : { return ( rdmdm ());                 /* from int bfr             */
 566 : }
 567 :
 568 : void Term_Comm ()             /* uninstall comm port drivers      */
 569 : { u_m ();
 570 : }
 571 :
 572 : /* end of cterm.c */



───────────────────────────────────────────────────────────────────────────


Figure 6-9.
CTERM.H.


/* CTERM.H - function prototypes for CTERM.C */
int Wants_To_Abort(void);
void main(int ,char * *);
int docmd(void);
int kbd_wait(void);
int kb_file(void);
int esc(void);
FILE *getfil(void);
void getnam(char *,int );
char *addext(char *,char *);
void put_cap(char );
void cap_flush(void);
long getmr(void);
void Delay(int );
void Start_Timer(int );
int Timer_Expired(void);
int Set_Vid(void);
void locate(int ,int );
void deol(void);
void deos(void);
void cls(void);
void cursor(int );
void revvid(int );
int putchx(char );
int Read_Keyboard(void);
void Init_Comm(void);
int Set_Baud(int );
int Set_Parity(int );
int Write_Modem(char );
int Read_Modem(void);
void Term_Comm(void);

/* CH1.ASM functions - modem interfacing */
void i_m(void);
void set_mdm(int);
void wrtmdm(int);
void Send_Byte(int);
int  rdmdm(void);
void u_m(void);

/* CH1A.ASM functions - exception handlers */
void set_int (void);
void rst_int (void);
int broke (void);

/* CH2.ASM functions - video interfacing */
void _i_v(void);
int  _wrpos(int, int);
void _deol(void);
void _cls(void);
int  _color(int, int);



───────────────────────────────────────────────────────────────────────────


Figure 8-6.
Subroutines illustrating Interrupt 21H Functions 4EH and 4FH.


                TITLE   'DIRS.ASM'

;
; Subroutines for DIRDUMP.C
;


ARG1            EQU     [bp + 4]        ; stack frame addressing for C argume
ARG2            EQU     [bp + 6]


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

;-------------------------------------------------------------------------
;
; void SetDTA( DTA );
;         char *DTA;
;
;-------------------------------------------------------------------------

                PUBLIC  _SetDTA
_SetDTA         PROC    near

                push    bp
                mov     bp,sp

                mov     dx,ARG1         ; DS:DX -> DTA
                mov     ah,1Ah          ; AH = INT 21H function number
                int     21h             ; pass DTA to MS-DOS

                pop     bp
                ret

_SetDTA         ENDP

;-------------------------------------------------------------------------
;
; int GetCurrentDir( *path );       /* returns error code */
;         char *path;               /* pointer to buffer to contain path */
;
;-------------------------------------------------------------------------

                PUBLIC  _GetCurrentDir
_GetCurrentDir  PROC    near

                push    bp
                mov     bp,sp
                push    si

                mov     si,ARG1         ; DS:SI -> buffer
                xor     dl,dl           ; DL = 0 (default drive number)
                mov     ah,47h          ; AH = INT 21H function number
                int     21h             ; call MS-DOS; AX = error code
                jc      L01             ; jump if error

                xor     ax,ax           ; no error, return AX = 0

L01:            pop     si
                pop     bp
                ret

_GetCurrentDir  ENDP

;-------------------------------------------------------------------------
;
; int FindFirstFile( path, attribute ); /* returns error code */
;         char *path;
;         int  attribute;
;
;-------------------------------------------------------------------------

                PUBLIC  _FindFirstFile
_FindFirstFile  PROC    near

                push    bp
                mov     bp,sp

                mov     dx,ARG1         ; DS:DX -> path
                mov     cx,ARG2         ; CX = attribute
                mov     ah,4Eh          ; AH = INT 21H function number
                int     21h             ; call MS-DOS; AX = error code
                jc      L02             ; jump if error
                xor     ax,ax           ; no error, return AX = 0

L02:            pop     bp
                ret

_FindFirstFile  ENDP

;-------------------------------------------------------------------------
;
; int FindNextFile();                   /* returns error code */
;
;-------------------------------------------------------------------------

                PUBLIC  _FindNextFile
_FindNextFile   PROC    near

                push    bp
                mov     bp,sp

                mov     ah,4Fh          ; AH = INT 21H function number
                int     21h             ; call MS-DOS; AX = error code
                jc      L03             ; jump if error

                xor     ax,ax           ; if no error, set AX = 0


L03:            pop     bp
                ret

_FindNextFile   ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

CurrentDir      DB      64 dup(?)
DTA             DB      64 dup(?)

_DATA           ENDS

                END



───────────────────────────────────────────────────────────────────────────


Figure 8-7.
The complete DIRDUMP.C program.


/* DIRDUMP.C */

#define AllAttributes   0x3F            /* bits set for all attributes */

main()
{
        static  char CurrentDir[64];
        int     ErrorCode;
        int     FileCount = 0;

        struct
        {
          char    reserved[21];
          char    attrib;
          int     time;
          int     date;
          long    size;
          char    name[13];
        }         DTA;

/* display current directory name */

        ErrorCode = GetCurrentDir( CurrentDir );
        if( ErrorCode )
        {
          printf( "\nError %d:  GetCurrentDir", ErrorCode );
          exit( 1 );
        }

        printf( "\nCurrent directory is \\%s", CurrentDir );


/* display files and attributes */

        SetDTA( &DTA );                 /* pass DTA to MS-DOS */

        ErrorCode = FindFirstFile( "*.*", AllAttributes );

        while( !ErrorCode )
        {
          printf( "\n%12s -- ", DTA.name );
          ShowAttributes( DTA.attrib );
          ++FileCount;

          ErrorCode = FindNextFile( );
        }

/* display file count and exit */

        printf( "\nCurrent directory contains %d files\n", FileCount );
        return( 0 );
}

ShowAttributes( a )
int     a;
{
        int     i;
        int     mask = 1;

        static char *AttribName[] =
        {
          "read-only ",
          "hidden ",
          "system ",
          "volume ",
          "subdirectory ",
          "archive "
        };


        for( i=0; i<6; i++ )          /* test each attribute bit */
        {
          if( a & mask )
            printf( AttribName[i] );  /* display a message if bit is set */
          mask = mask << 1;
        }
}



───────────────────────────────────────────────────────────────────────────


Figure 8-8.
Subroutines for manipulating volume labels.


                TITLE   'VOLS.ASM'

;-------------------------------------------------------------------------
;
; C-callable routines for manipulating MS-DOS volume labels.
; Note: These routines modify the current DTA address.
;
;-------------------------------------------------------------------------

ARG1            EQU     [bp + 4]        ; stack frame addressing

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

;--------------------------------------------------------------------------
;
; char *GetVolLabel();         /* returns pointer to volume label name */
;
;-------------------------------------------------------------------------

                PUBLIC  _GetVolLabel
_GetVolLabel    PROC    near

                push    bp
                mov     bp,sp
                push    si
                push    di

                call    SetDTA          ; pass DTA address to MS-DOS
                mov     dx,offset DGROUP:ExtendedFCB
                mov     ah,11h          ; AH = INT 21H function number
                int     21h             ; Search for First Entry
                test    al,al
                jnz     L01
                                        ; label found so make a copy
                mov     si,offset DGROUP:DTA + 8
                mov     di,offset DGROUP:VolLabel
                call    CopyName
                mov     ax,offset DGROUP:VolLabel   ; return copy's address
                jmp     short L02

L01:            xor     ax,ax           ; no label, return 0 (null pointer)

L02:            pop     di
                pop     si
                pop     bp
                ret

_GetVolLabel    ENDP

;-------------------------------------------------------------------------
;
; int RenameVolLabel( label );       /* returns error code */
;         char *label;               /* pointer to new volume label name */
;
;-------------------------------------------------------------------------

                PUBLIC  _RenameVolLabel
_RenameVolLabel PROC    near

                push    bp
                mov     bp,sp
                push    si
                push    di
                mov     si,offset DGROUP:VolLabel   ; DS:SI -> old
                                                    ; volume name
                mov     di,offset DGROUP:Name1
                call    CopyName        ; copy old name to FCB

                mov     si,ARG1
                mov     di,offset DGROUP:Name2
                call    CopyName        ; copy new name into FCB

                mov     dx,offset DGROUP:ExtendedFCB    ; DS:DX -> FCB
                mov     ah,17h       ; AH = INT 21H function number
                int     21h          ; rename
                xor     ah,ah        ; AX = 00H (success) or 0FFH (failure)

                pop     di           ; restore registers and return
                pop     si
                pop     bp
                ret

_RenameVolLabel ENDP

;-------------------------------------------------------------------------
;
; int NewVolLabel( label );             /* returns error code */
;         char *label;                  /* pointer to new volume label
;                                       /* name */
;
;-------------------------------------------------------------------------

                PUBLIC  _NewVolLabel
_NewVolLabel    PROC    near

                push    bp
                mov     bp,sp
                push    si
                push    di

                mov     si,ARG1
                mov     di,offset DGROUP:Name1
                call    CopyName        ; copy new name to FCB

                mov     dx,offset DGROUP:ExtendedFCB
                mov     ah,16h          ; AH = INT 21H function number
                int     21h             ; create directory entry
                xor     ah,ah           ; AX = 00H (success) or 0FFH
                                        ; (failure)

                pop     di              ; restore registers and return
                pop     si
                pop     bp
                ret

_NewVolLabel    ENDP

;-------------------------------------------------------------------------
;
; int DeleteVolLabel();                 /* returns error code */
;
;-------------------------------------------------------------------------

                PUBLIC  _DeleteVolLabel
_DeleteVolLabel PROC    near

                push    bp
                mov     bp,sp
                push    si
                push    di

                mov     si,offset DGROUP:VolLabel
                mov     di,offset DGROUP:Name1
                call    CopyName        ; copy current volume name to FCB

                mov     dx,offset DGROUP:ExtendedFCB
                mov     ah,13h          ; AH = INT 21H function number
                int     21h             ; delete directory entry
                xor     ah,ah           ; AX = 00H (success) or 0FFH
                                        ; (failure)

                pop     di              ; restore registers and return
                pop     si
                pop     bp
                ret

_DeleteVolLabel ENDP

;-------------------------------------------------------------------------
;
; miscellaneous subroutines
;
;-------------------------------------------------------------------------

SetDTA          PROC    near

                push    ax              ; preserve registers used
                push    dx

                mov     dx,offset DGROUP:DTA    ; DS:DX -> DTA
                mov     ah,1Ah          ; AH = INT 21H function number
                int     21h             ; set DTA

                pop     dx              ; restore registers and return
                pop     ax
                ret

SetDTA          ENDP

CopyName        PROC    near            ; Caller:  SI -> ASCIIZ source
                                        ;          DI -> destination
                push    ds
                pop     es              ; ES = DGROUP
                mov     cx,11           ; length of name field

L11:            lodsb                   ; copy new name into FCB ..
                test    al,al
                jz      L12             ; .. until null character is
                                        ; reached
                stosb
                loop    L11

L12:            mov     al,' '          ; pad new name with blanks
                rep     stosb
                ret

CopyName        ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

VolLabel        DB      11 dup(0),0

ExtendedFCB     DB      0FFh            ; must be 0FFH for extended FCB
                DB      5 dup(0)        ; (reserved)
                DB      1000b           ; attribute byte (bit 3 = 1)
                DB      0               ; default drive ID
Name1           DB      11 dup('?')     ; global wildcard name
                DB      5 dup(0)        ; (unused)
Name2           DB      11 dup(0)       ; second name (for renaming entry)
                DB      9 dup(0)        ; (unused)

DTA             DB      64 dup(0)

_DATA           ENDS

                END



───────────────────────────────────────────────────────────────────────────


Figure 9-2.
An example of a .COM program releasing excess memory after it
receives control from MS-DOS. Interrupt 21H Function 4AH is called
with the segment address of the program's PSP in register ES and the
number of paragraphs of memory to retain in register BX.


        .
        .
        .
_TEXT   segment para public 'CODE'

        org     100h

        assume  cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT

main    proc    near            ; entry point from MS-DOS
                                ; CS = DS = ES = SS = PSP

                                ; first move our stack
        mov     sp,offset stk   ; to a safe place...

                                ; now release extra memory...
        mov     bx,offset stk   ; calculate paragraphs to keep
        mov     cl,4            ; (divide offset of end of
        shr     bx,cl           ; program by 16 and round up)
        inc     bx
        mov     ah,4ah          ; Fxn 4AH = resize mem block
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if resize failed
        .
        .                       ; otherwise go on with work...
        .
main    endp

        .
        .
        .

        dw      64 dup (?)
stk     equ     $               ; base of new stack area

_TEXT   ends

        end     main            ; defines program entry point



───────────────────────────────────────────────────────────────────────────


Figure 9-3.
An example of a .EXE program releasing excess memory after it
receives control from MS-DOS. This particular code sequence depends
on the segment order shown. When a .EXE program is linked from many
different object modules, other techniques may be needed  to determine
the amount of memory occupied by the program  at run time.


_TEXT   segment word public 'CODE'     ; executable code segment

        assume  cs:_TEXT,ds:_DATA,ss:_STACK

main    proc    far             ; entry point from MS-DOS
                                ; CS = _TEXT segment,
                                ; DS = ES = PSP

        mov     ax,_DATA        ; set DS = our data segment
        mov     ds,ax

                                ; give back extra memory...
        mov     ax,es           ; let AX = segment of PSP base
        mov     bx,ss           ; and BX = segment of stack base
        sub     bx,ax           ; reserve seg stack - seg psp
        add     bx,stksize/16   ; plus paragraphs of stack
        inc     bx              ; round up
        mov     ah,4ah          ; Fxn 4AH = resize memory block
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if resize failed

        .
        .
        .

main    endp

_TEXT   ends

_DATA   segment word public 'DATA'     ; static & variable data

        .
        .
        .

_DATA   ends

STACK   segment para stack 'STACK'

        db      stksize dup (?)

STACK   ends

        end     main                   ; defines program entry point



───────────────────────────────────────────────────────────────────────────


Figure 9-6.
Testing for the presence of the Expanded Memory Manager with the MS-DOS
Open File or Device (Interrupt 21H Function 3DH) and IOCTL (Interrupt 21H
Function 44H) functions.

        .
        .
        .
                                ; attempt to "open" EMM...
        mov     dx,seg emm_name ; DS:DX = address of name
        mov     ds,dx           ; of EMM
        mov     dx,offset emm_name
        mov     ax,3d00h        ; Fxn 3DH, Mode = 00H
                                ; = open, read-only
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if open failed

                                ; open succeeded, make sure
                                ; it was not a file...
        mov     bx,ax           ; BX = handle from open
        mov     ax,4400h        ; Fxn 44H Subfxn 00H
                                ; = IOCTL Get Device Information
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if IOCTL call failed
        and     dx,80h          ; Bit 7 = 1 if character device
        jz      error           ; jump if it was a file

                                ; EMM is present, make sure
                                ; it is available...
                                ; (BX still contains handle)
        mov     ax,4407h        ; Fxn 44H Subfxn 07H
                                ; = IOCTL Get Output Status
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if IOCTL call failed
        or      al,al           ; test device status
        jz      error           ; if AL = 0 EMM is not available

                                ; now close handle ...
                                ; (BX still contains handle)
        mov     ah,3eh          ; Fxn 3EH = Close
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if close failed
        .
        .
        .

emm_name db     'EMMXXXX0',0    ; guaranteed device name for EMM



───────────────────────────────────────────────────────────────────────────


Figure 9-7.
Testing for the presence of the Expanded Memory Manager by inspecting the
name field in the device driver header.


emm_int equ     67h             ; EMM software interrupt

        .
        .
        .
                                ; first fetch contents of
                                ; EMM interrupt vector...
        mov     al,emm_int      ; AL = EMM int number
        mov     ah,35h          ; Fxn 35H = get vector
        int     21h             ; transfer to MS-DOS
                                ; now ES:BX = handler address

                                ; assume ES:0000 points
                                ; to base of the EMM...
        mov     di,10           ; ES:DI = address of name
                                ; field in device header
        mov     si,seg emm_name ; DS:SI = address of
        mov     ds,si           ; expected EMM driver name
        mov     si,offset emm_name
        mov     cx,8            ; length of name field
        cld
        repz cmpsb              ; compare names...
        jnz     error           ; jump if driver absent
        .
        .
        .


emm_name db     'EMMXXXX0'      ; guaranteed device name for EMM



───────────────────────────────────────────────────────────────────────────


Figure 9-8.
A program skeleton for the use of expanded memory. This code assumes
that the presence of the Expanded Memory Manager has already been ver-
ified with one of the techniques shown in Figures 9-6 and 9-7.


        .
        .
        .
        mov     ah,40h          ; test EMM status
        int     67h
        or      ah,ah
        jnz     error           ; jump if bad status from EMM

        mov     ah,46h          ; check EMM version
        int     67h
        or      ah,ah
        jnz     error           ; jump if couldn't get version
        cmp     al,30h          ; make sure at least ver. 3.0
        jb      error           ; jump if wrong EMM version

        mov     ah,41h          ; get page frame segment
        int     67h
        or      ah,ah
        jnz     error           ; jump if failed to get frame
        mov     page_frame,bx   ; save segment of page frame

        mov     ah,42h          ; get no. of available pages
        int     67h
        or      ah,ah
        jnz     error           ; jump if get pages error
        mov     total_pages,dx  ; save total EMM pages
        mov     avail_pages,bx  ; save available EMM pages
        or      bx,bx
        jz      error           ; abort if no pages available

        mov     ah,43h          ; try to allocate EMM pages
        mov     bx,needed_pages
        int     67h             ; if allocation is successful
        or      ah,ah
        jnz     error           ; jump if allocation failed

        mov     emm_handle,dx   ; save handle for allocated pages

        .
        .                       ; now we are ready for other
        .                       ; processing using EMM pages
        .
                                ; map in EMM memory page...
        mov     bx,log_page     ; BX <- EMM logical page number
        mov     al,phys_page    ; AL <- EMM physical page (0-3)
        mov     dx,emm_handle   ; EMM handle for our pages
        mov     ah,44h          ; Fxn 44H = map EMM page
        int     67h
        or      ah,ah
        jnz     error           ; jump if mapping error

        .
        .
        .
                                ; program ready to terminate,
                                ; give up allocated EMM pages...
        mov     dx,emm_handle   ; handle for our pages
        mov     ah,45h          ; EMM Fxn 45H = release pages
        int     67h
        or      ah,ah
        jnz     error           ; jump if release failed
        .
        .
        .



───────────────────────────────────────────────────────────────────────────


Figure 9-9.
Demonstration of a block move from extended memory to conventional memory
using the ROM BIOS routine. The procedure getblk accepts a source address
in extended memory, a destination address in conventional memory, a length
in bytes, and the segment and offset of a block move descriptor table. The
extended memory address is a linear 32-bit address, of which only the lower
24 bits are significant; the conventional-memory address is a segment and
offset. The getblk routine converts the destination segment and off- set
to a linear address, builds the appropriate fields in the block move
descriptor table, invokes the ROM BIOS routine to perform the transfer,
and returns the status in the AH register.


                                ; block move descriptor table
bmdt    db      8 dup (0)       ; dummy descriptor
        db      8 dup (0)       ; GDT descriptor
        db      8 dup (0)       ; source segment descriptor
        db      8 dup (0)       ; destination segment descriptor
        db      8 dup (0)       ; BIOS CS segment descriptor
        db      8 dup (0)       ; BIOS SS segment descriptor

buff    db      80h dup (0)     ; buffer to receive data

        .
        .
        .
        mov     dx,10h          ; DX:AX = source extended memory
        mov     ax,0            ; address 100000H (1 MB)
        mov     bx,seg buff     ; DS:BX = destination conventional
        mov     ds,bx           ; memory address
        mov     bx,offset buff
        mov     cx,80h          ; CX = length to move (bytes)
        mov     si,seg bmdt     ; ES:SI = block move descriptor table
        mov     es,si
        mov     si,offset bmdt
        call    getblk          ; get block from extended memory
        or      ah,ah           ; test status
        jnz     error           ; jump if block move failed
        .
        .
        .

getblk  proc    near            ; transfer block from extended
                                ; memory to real memory
                                ; call with
                                ; DX:AX = extended memory address
                                ; DS:BX = destination buffer
                                ;    CX = length (bytes)
                                ; ES:SI = block move descriptor table
                                ; returns
                                ;    AH = 0 if transfer OK
        mov     es:[si+10h],cx  ; store length in descriptors
        mov     es:[si+18h],cx
                                ; store access rights bytes
        mov     byte ptr es:[si+15h],93h
        mov     byte ptr es:[si+1dh],93h
                                ; source (extended memory) address
        mov     es:[si+12h],ax
        mov     es:[si+14h],dl
                                ; destination (conv memory) address
        mov     ax,ds           ; segment * 16
        mov     dx,16
        mul     dx
        add     ax,bx           ; + offset -> linear address
        adc     dx,0
        mov     es:[si+1ah],ax
        mov     es:[si+1ch],dl

        shr     cx,1            ; convert length to words
        mov     ah,87h          ; Fxn 87H = block move
        int     15h             ; transfer to ROM BIOS

        ret                     ; back to caller



───────────────────────────────────────────────────────────────────────────


Figure 9-4.
A skeleton example of dynamic memory allocation. The program requests a
32 KB memory block, uses it to copy its working file to a backup file, and
then releases the memory block. Note the use of ASSUME directives to force
the assembler to generate proper segment overrides on references to
variables containing file handles.


        .
        .
        .
        mov     bx,800h         ; 800H paragraphs = 32 KB
        mov     ah,48h          ; Fxn 48H = allocate block
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if allocation failed
        mov     bufseg,ax       ; save segment of block

                                ; open working file...
        mov     dx,offset file1 ; DS:DX = filename address
        mov     ax,3d00h        ; Fxn 3DH = open, read only
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if open failed
        mov     handle1,ax      ; save handle for work file
                                ; create backup file...
        mov     dx,offset file2 ; DS:DX = filename address
        mov     cx,0            ; CX = attribute (normal)
        mov     ah,3ch          ; Fxn 3CH = create file
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if create failed
        mov     handle2,ax      ; save handle for backup file

        push    ds              ; set ES = our data segment
        pop     es
        mov     ds,bufseg       ; set DS:DX = allocated block
        xor     dx,dx

        assume  ds:NOTHING,es:_DATA     ; tell assembler

next:                           ; read working file...
        mov     bx,handle1      ; handle for work file
        mov     cx,8000h        ; try to read 32 KB
        mov     ah,3fh          ; Fxn 3FH = read
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if read failed
        or      ax,ax           ; was end of file reached?
        jz      done            ; yes, exit this loop

                                ; now write backup file...
        mov     cx,ax           ; set write length = read length
        mov     bx,handle2      ; handle for backup file
        mov     ah,40h          ; Fxn 40H = write
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if write failed
        cmp     ax,cx           ; was write complete?
        jne     error           ; no, disk must be full
        jmp     next            ; transfer another record

done:   push    es              ; restore DS = data segment
        pop     ds

        assume  ds:_DATA,es:NOTHING     ; tell assembler

                                ; release allocated block...
        mov     es,bufseg       ; segment base of block
        mov     ah,49h          ; Fxn 49H = release block
        int     21h             ; transfer to MS-DOS
        jc      error           ; (should never fail)

                                ; now close backup file...
        mov     bx,handle2      ; handle for backup file
        mov     ah,3eh          ; Fxn 3EH = close
        int     21h             ; transfer to MS-DOS
        jc      error           ; jump if close failed

        .
        .
        .

file1   db      'MYFILE.DAT',0  ; name of working file
file2   db      'MYFILE.BAK',0  ; name of backup file

handle1 dw      ?               ; handle for working file
handle2 dw      ?               ; handle for backup file
bufseg  dw      ?               ; segment of allocated block



───────────────────────────────────────────────────────────────────────────


Figure 10-3.
PARENT.ASM, source code for PARENT.EXE.


         name      parent
         title     'PARENT --- demonstrate EXEC call'
;
; PARENT.EXE --- demonstration of EXEC to run process
;
; Uses MS-DOS EXEC (Int 21H Function 4BH Subfunction 00H)
; to load and execute a child process named CHILD.EXE,
; then displays CHILD's return code.
;
; Ray Duncan, June 1987
;

stdin   equ     0                       ; standard input
stdout  equ     1                       ; standard output
stderr  equ     2                       ; standard error

stksize equ     128                     ; size of stack

cr      equ     0dh                     ; ASCII carriage return
lf      equ     0ah                     ; ASCII linefeed


DGROUP  group   _DATA,_ENVIR,_STACK


_TEXT   segment byte public 'CODE'     ; executable code segment

        assume  cs:_TEXT,ds:_DATA,ss:_STACK


stk_seg dw      ?                       ; original SS contents
stk_ptr dw      ?                       ; original SP contents


main    proc    far                     ; entry point from MS-DOS

        mov     ax,_DATA                ; set DS = our data segment
        mov     ds,ax

                                        ; now give back extra memory
                                        ; so child has somewhere to run...
        mov     ax,es                   ; let AX = segment of PSP base
        mov     bx,ss                   ; and BX = segment of stack base
        sub     bx,ax                   ; reserve seg stack - seg psp
        add     bx,stksize/16           ; plus paragraphs of stack
        mov     ah,4ah                  ; fxn 4AH = modify memory block
        int     21h
        jc      main1
                                        ; display parent message ...
        mov     dx,offset DGROUP:msg1   ; DS:DX = address of message
        mov     cx,msg1_len             ; CX = length of message
        call    pmsg

        push    ds                      ; save parent's data segment
        mov     stk_seg,ss              ; save parent's stack pointer
        mov     stk_ptr,sp

                                        ; now EXEC the child process...
        mov     ax,ds                   ; set ES = DS
        mov     es,ax
        mov     dx,offset DGROUP:cname  ; DS:DX = child pathname
        mov     bx,offset DGROUP:pars   ; ES:BX = parameter block
        mov     ax,4b00h                ; function 4BH subfunction 00H
        int     21h                     ; transfer to MS-DOS

        cli                             ; (for bug in some early 8088s)
        mov     ss,stk_seg              ; restore parent's stack pointer
        mov     sp,stk_ptr
        sti                             ; (for bug in some early 8088s)
        pop     ds                      ; restore DS = our data segment

        jc      main2                   ; jump if EXEC failed

                                        ; otherwise EXEC succeeded,
                                        ; convert and display child's
                                        ; termination and return codes...
        mov     ah,4dh                  ; fxn 4DH = get return code
        int     21h                     ; transfer to MS-DOS
        xchg    al,ah                   ; convert termination code
        mov     bx,offset DGROUP:msg4a
        call    b2hex
        mov     al,ah                   ; get back return code
        mov     bx,offset DGROUP:msg4b  ; and convert it
        call    b2hex
        mov     dx,offset DGROUP:msg4   ; DS:DX = address of message
        mov     cx,msg4_len             ; CX = length of message
        call    pmsg                    ; display it

        mov     ax,4c00h                ; no error, terminate program
        int     21h                     ; with return code = 0

main1:  mov     bx,offset DGROUP:msg2a  ; convert error code
        call    b2hex
        mov     dx,offset DGROUP:msg2   ; display message 'Memory
        mov     cx,msg2_len             ; resize failed...'
        call    pmsg
        jmp     main3

main2:  mov     bx,offset DGROUP:msg3a  ; convert error code
        call    b2hex
        mov     dx,offset DGROUP:msg3   ; display message 'EXEC
        mov     cx,msg3_len             ; call failed...'
        call    pmsg

main3:  mov     ax,4c01h                ; error, terminate program
        int     21h                     ; with return code = 1

main    endp                            ; end of main procedure


b2hex   proc    near                    ; convert byte to hex ASCII
                                        ; call with AL = binary value
                                        ;           BX = addr to store
                                        ;           string
        push    ax
        shr     al,1
        shr     al,1
        shr     al,1
        shr     al,1
        call    ascii                   ; become first ASCII character
        mov     [bx],al                 ; store it
        pop     ax
        and     al,0fh                  ; isolate lower 4 bits, which
        call    ascii                   ; become the second ASCII character
        mov     [bx+1],al               ; store it
        ret
b2hex   endp


ascii   proc    near                    ; convert value 00-0FH in AL
        add     al,'0'                  ; into a "hex ASCII" character
        cmp     al,'9'
        jle     ascii2                  ; jump if in range 00-09H,
        add     al,'A'-'9'-1            ; offset it to range 0A-0FH,

ascii2: ret                             ; return ASCII char. in AL
ascii   endp


pmsg    proc    near                    ; displays message on standard
                                        ; output
                                        ; call with DS:DX = address,
                                        ;              CX = length
        mov     bx,stdout               ; BX = standard output handle
        mov     ah,40h                  ; function 40H = write file/device
        int     21h                     ; transfer to MS-DOS
        ret                             ; back to caller

pmsg    endp

_TEXT   ends


_DATA   segment para public 'DATA'      ; static & variable data segment

cname   db      'CHILD.EXE',0           ; pathname of child process

pars    dw      _ENVIR                  ; segment of environment block
        dd      tail                    ; long address, command tail
        dd      fcb1                    ; long address, default FCB #1
        dd      fcb2                    ; long address, default FCB #2

tail    db      fcb1-tail-2             ; command tail for child
        db      'dummy command tail',cr

fcb1    db      0                       ; copied into default FCB #1 in
        db      11 dup (' ')            ; child's program segment prefix
        db      25 dup (0)

fcb2    db      0                       ; copied into default FCB #2 in
        db      11 dup (' ')            ; child's program segment prefix
        db      25 dup (0)

msg1    db      cr,lf,'Parent executing!',cr,lf
msg1_len equ    $-msg1

msg2    db      cr,lf,'Memory resize failed, error code='
msg2a   db      'xxh.',cr,lf
msg2_len equ    $-msg2

msg3    db      cr,lf,'EXEC call failed, error code='
msg3a   db      'xxh.',cr,lf
msg3_len equ    $-msg3

msg4    db      cr,lf,'Parent regained control!'
        db      cr,lf,'Child termination type='
msg4a   db      'xxh, return code='
msg4b   db      'xxh.',cr,lf
msg4_len equ    $-msg4

_DATA   ends


_ENVIR  segment para public 'DATA'      ; example environment block
                                        ; to be passed to child
        db      'PATH=',0               ; basic PATH, PROMPT,
        db      'PROMPT=$p$_$n$g',0     ; and COMSPEC strings
        db      'COMSPEC=C:\COMMAND.COM',0
        db      0                       ; extra null terminates block

_ENVIR  ends


_STACK  segment para stack 'STACK'

        db      stksize dup (?)

_STACK  ends


        end     main                    ; defines program entry point



───────────────────────────────────────────────────────────────────────────


Figure 10-4.
CHILD.ASM, source code for CHILD.EXE.


         name      child
         title     'CHILD process'
;
; CHILD.EXE --- a simple process loaded by PARENT.EXE
; to demonstrate the MS-DOS EXEC call, Subfunction 00H.
;
; Ray Duncan, June 1987
;

stdin   equ     0                       ; standard input
stdout  equ     1                       ; standard output
stderr  equ     2                       ; standard error

cr      equ     0dh                     ; ASCII carriage return
lf      equ     0ah                     ; ASCII linefeed


DGROUP  group   _DATA,STACK


_TEXT   segment byte public 'CODE'      ; executable code segment

        assume  cs:_TEXT,ds:_DATA,ss:STACK

main    proc    far                     ; entry point from MS-DOS

        mov     ax,_DATA                ; set DS = our data segment
        mov     ds,ax

                                        ; display child message ...

        mov     dx,offset msg           ; DS:DX = address of message
        mov     cx,msg_len              ; CX = length of message
        mov     bx,stdout               ; BX = standard output handle
        mov     ah,40h                  ; AH = fxn 40H, write file/device
        int     21h                     ; transfer to MS-DOS
        jc      main2                   ; jump if any error

        mov     ax,4c00h                ; no error, terminate child
        int     21h                     ; with return code = 0

main2:  mov     ax,4c01h                ; error, terminate child
        int     21h                     ; with return code = 1

main    endp                            ; end of main procedure

_TEXT   ends


_DATA   segment para public 'DATA'     ; static & variable data segment

msg     db      cr,lf,'Child executing!',cr,lf
msg_len equ     $-msg

_DATA   ends


STACK   segment para stack 'STACK'

        dw      64 dup (?)

STACK   ends


        end     main                    ; defines program entry point



───────────────────────────────────────────────────────────────────────────


Figure 10-5.
ROOT.ASM, source code for ROOT.EXE.


         name      root
         title     'ROOT --- demonstrate EXEC overlay'
;
; ROOT.EXE --- demonstration of EXEC for overlays
;
; Uses MS-DOS EXEC (Int 21H Function 4BH Subfunction 03H)
; to load an overlay named OVERLAY.OVL, calls a routine
; within the OVERLAY, then recovers control and terminates.
;
; Ray Duncan, June 1987
;

stdin   equ     0                       ; standard input
stdout  equ     1                       ; standard output
stderr  equ     2                       ; standard error

stksize equ     128                     ; size of stack

cr      equ     0dh                     ; ASCII carriage return
lf      equ     0ah                     ; ASCII linefeed


DGROUP  group   _DATA,_STACK


_TEXT   segment byte public 'CODE'      ; executable code segment

        assume  cs:_TEXT,ds:_DATA,ss:_STACK


stk_seg dw      ?                       ; original SS contents
stk_ptr dw      ?                       ; original SP contents

main    proc    far                     ; entry point from MS-DOS

        mov     ax,_DATA                ; set DS = our data segment
        mov     ds,ax

                                        ; now give back extra memory
        mov     ax,es                   ; AX = segment of PSP base
        mov     bx,ss                   ; BX = segment of stack base
        sub     bx,ax                   ; reserve seg stack - seg psp
        add     bx,stksize/16           ; plus paragraphs of stack
        mov     ah,4ah                  ; fxn 4AH = modify memory block
        int     21h                     ; transfer to MS-DOS
        jc      main1                   ; jump if resize failed

                                        ; display message 'Root
                                        ; segment executing...'
        mov     dx,offset DGROUP:msg1   ; DS:DX = address of message
        mov     cx,msg1_len             ; CX = length of message
        call    pmsg

                                        ; allocate memory for overlay
        mov     bx,1000h                ; get 64 KB (4096 paragraphs)
        mov     ah,48h                  ; fxn 48H, allocate mem block
        int     21h                     ; transfer to MS-DOS
        jc      main2                   ; jump if allocation failed

        mov     pars,ax                 ; set load address for overlay
        mov     pars+2,ax               ; set relocation segment for
                                        ; overlay
        mov     word ptr entry+2,ax     ; set segment of entry point

        push    ds                      ; save root's data segment
        mov     stk_seg,ss              ; save root's stack pointer
        mov     stk_ptr,sp

                                        ; now use EXEC to load overlay
        mov     ax,ds                   ; set ES = DS
        mov     es,ax
        mov     dx,offset DGROUP:oname  ; DS:DX = overlay pathname
        mov     bx,offset DGROUP:pars   ; ES:BX = parameter block
        mov     ax,4b03h                ; function 4BH, subfunction 03H
        int     21h                     ; transfer to MS-DOS

        cli                             ; (for bug in some early 8088s)
        mov     ss,stk_seg              ; restore root's stack pointer
        mov     sp,stk_ptr
        sti                             ; (for bug in some early 8088s)
        pop     ds                      ; restore DS = our data segment

        jc      main3                   ; jump if EXEC failed

                                        ; otherwise EXEC succeeded...

        push    ds                      ; save our data segment
        call    dword ptr entry         ; now call the overlay
        pop     ds                      ; restore our data segment

                                        ; display message that root
                                        ; segment regained control...
        mov     dx,offset DGROUP:msg5   ; DS:DX = address of message
        mov     cx,msg5_len             ; CX = length of message
        call    pmsg                    ; display it

        mov     ax,4c00h                ; no error, terminate program
        int     21h                     ; with return code = 0

main1:  mov     bx,offset DGROUP:msg2a  ; convert error code
        call    b2hex
        mov     dx,offset DGROUP:msg2   ; display message 'Memory
        mov     cx,msg2_len             ; resize failed...'
        call    pmsg
        jmp     main4

main2:  mov     bx,offset DGROUP:msg3a  ; convert error code
        call    b2hex
        mov     dx,offset DGROUP:msg3   ; display message 'Memory
        mov     cx,msg3_len             ; allocation failed...'
        call    pmsg
        jmp     main4

main3:  mov     bx,offset DGROUP:msg4a  ; convert error code
        call    b2hex
        mov     dx,offset DGROUP:msg4   ; display message 'EXEC
        mov     cx,msg4_len             ; call failed...'
        call    pmsg

main4:  mov     ax,4c01h                ; error, terminate program
        int     21h                     ; with return code = 1

main    endp                            ; end of main procedure


b2hex   proc    near                    ; convert byte to hex ASCII
                                        ; call with AL = binary value
                                        ; BX = addr to store string
        push    ax
        shr     al,1
        shr     al,1
        shr     al,1
        shr     al,1
        call    ascii                   ; become first ASCII character
        mov     [bx],al                 ; store it
        pop     ax
        and     al,0fh                  ; isolate lower 4 bits, which
        call    ascii                   ; become the second ASCII character
        mov     [bx+1],al               ; store it
        ret
b2hex   endp


ascii   proc    near                    ; convert value 00-0FH in AL
        add     al,'0'                  ; into a "hex ASCII" character
        cmp     al,'9'
        jle     ascii2                  ; jump if in range 00-09H,
        add     al,'A'-'9'-1            ; offset it to range 0A-0FH,
ascii2: ret                             ; return ASCII char. in AL.
ascii   endp


pmsg    proc    near                    ; displays message on
                                        ; standard output
                                        ; call with DS:DX = address,
                                        ;              CX = length

        mov     bx,stdout               ; BX = standard output handle
        mov     ah,40h                  ; function 40H = write file/device
        int     21h                     ; transfer to MS-DOS
        ret                             ; back to caller

pmsg    endp

_TEXT   ends


_DATA   segment para public 'DATA'      ; static & variable data segment

oname   db      'OVERLAY.OVL',0         ; pathname of overlay file

pars    dw      0                       ; load address (segment) for file
        dw      0                       ; relocation (segment) for file

entry   dd      0                       ; entry point for overlay

msg1    db      cr,lf,'Root segment executing!',cr,lf
msg1_len equ    $-msg1

msg2    db      cr,lf,'Memory resize failed, error code='
msg2a   db      'xxh.',cr,lf
msg2_len equ    $-msg2

msg3    db      cr,lf,'Memory allocation failed, error code='
msg3a   db      'xxh.',cr,lf
msg3_len equ    $-msg3

msg4    db      cr,lf,'EXEC call failed, error code='
msg4a   db      'xxh.',cr,lf
msg4_len equ    $-msg4

msg5    db      cr,lf,'Root segment regained control!',cr,lf
msg5_len equ    $-msg5

_DATA   ends


_STACK  segment para stack 'STACK'

        db      stksize dup (?)

_STACK  ends


        end     main                    ; defines program entry point



───────────────────────────────────────────────────────────────────────────


Figure 10-6.
OVERLAY.ASM, source code for OVERLAY.OVL.


         name      overlay
         title     'OVERLAY segment'
;
; OVERLAY.OVL --- a simple overlay segment
; loaded by ROOT.EXE to demonstrate use of
; the MS-DOS EXEC call Subfunction 03H.
;
; The overlay does not contain a STACK segment
; because it uses the ROOT segment's stack.
;
; Ray Duncan, June 1987
;

stdin   equ     0                       ; standard input
stdout  equ     1                       ; standard output
stderr  equ     2                       ; standard error

cr      equ     0dh                     ; ASCII carriage return
lf      equ     0ah                     ; ASCII linefeed


_TEXT   segment byte public 'CODE'       ; executable code segment

        assume  cs:_TEXT,ds:_DATA
ovlay   proc    far                     ; entry point from root segment

        mov     ax,_DATA                ; set DS = local data segment
        mov     ds,ax
                                        ; display overlay message ...
        mov     dx,offset msg           ; DS:DX = address of message
        mov     cx,msg_len              ; CX = length of message
        mov     bx,stdout               ; BX = standard output handle
        mov     ah,40h                  ; AH = fxn 40H, write file/device
        int     21h                     ; transfer to MS-DOS

        ret                             ; return to root segment

ovlay   endp                            ; end of ovlay procedure

_TEXT   ends


_DATA   segment para public 'DATA'      ; static & variable data segment

msg     db      cr,lf,'Overlay executing!',cr,lf
msg_len equ     $-msg

_DATA   ends

        end



───────────────────────────────────────────────────────────────────────────


Figure 11-3.
HELLO.ASM, a passive TSR.


;
; Name:          hello
;
; Description:   This RAM-resident (terminate-and-stay-resident) utility
;                displays the message "Hello, World" in response to a
;                software interrupt.
;
; Comments:      Assemble and link to create HELLO.EXE.
;
;                Execute HELLO.EXE to make resident.
;
;                Execute  INT 64h  to display the message.
;


TSRInt           EQU     64h
STDOUT           EQU     1

RESIDENT_TEXT    SEGMENT byte public 'CODE'
                 ASSUME  cs:RESIDENT_TEXT,ds:RESIDENT_DATA

TSRAction        PROC    far

                 sti                    ; enable interrupts

                 push    ds             ; preserve registers
                 push    ax
                 push    bx
                 push    cx
                 push    dx

                 mov     dx,seg RESIDENT_DATA
                 mov     ds,dx
                 mov     dx,offset Message      ; DS:DX -> message
                 mov     cx,16                  ; CX = length
                 mov     bx,STDOUT              ; BX = file handle
                 mov     ah,40h                 ; AH = INT 21H function 40H
                                                ; (Write File)
                 int     21h                    ; display the message

                 pop     dx                     ; restore registers and
                                                ; exit
                 pop     cx
                 pop     bx
                 pop     ax
                 pop     ds
                 iret

TSRAction        ENDP

RESIDENT_TEXT    ENDS


RESIDENT_DATA    SEGMENT word public 'DATA'

Message          DB      0Dh,0Ah,'Hello, World',0Dh,0Ah

RESIDENT_DATA    ENDS


TRANSIENT_TEXT   SEGMENT para public 'TCODE'
                 ASSUME  cs:TRANSIENT_TEXT,ss:TRANSIENT_STACK

HelloTSR PROC    far                    ; At entry:     CS:IP -> SnapTSR
                                        ;               SS:SP -> stack
                                        ;               DS,ES -> PSP
; Install this TSR's interrupt handler

                 mov     ax,seg RESIDENT_TEXT
                 mov     ds,ax
                 mov     dx,offset RESIDENT_TEXT:TSRAction
                 mov     al,TSRInt
                 mov     ah,25h
                 int     21h

; Terminate and stay resident

                 mov     dx,cs          ; DX = paragraph address of start
                                        ; of transient portion (end of
                                        ; resident portion)
                 mov     ax,es          ; ES = PSP segment
                 sub     dx,ax          ; DX = size of resident portion
                 mov     ax,3100h       ; AH = INT 21H function number
                                        ; (TSR)
                                        ; AL = 00H (return code)
                 int     21h

HelloTSR         ENDP

TRANSIENT_TEXT   ENDS


TRANSIENT_STACK  SEGMENT word stack 'TSTACK'

                 DB      80h dup(?)

TRANSIENT_STACK  ENDS


                 END     HelloTSR



───────────────────────────────────────────────────────────────────────────


Figure 11-4.
SNAP.ASM, a video snapshot TSR.


;
; Name:         snap
;
; Description:  This RAM-resident (terminate-and-stay-resident) utility
;               produces a video "snapshot" by copying the contents of the
;               video regeneration buffer to a disk file.  It may be used
;               in 80-column alphanumeric video modes on IBM PCs and PS/2s.
;
; Comments:     Assemble and link to create SNAP.EXE.
;
;               Execute SNAP.EXE to make resident.
;
;               Press Alt-Enter to dump current contents of video buffer
;               to a disk file.
;

MultiplexID     EQU     0CAh            ; unique INT 2FH ID value

TSRStackSize    EQU     100h            ; resident stack size in bytes

KB_FLAG         EQU     17h             ; offset of shift-key status flag
                                        ; in ROM BIOS keyboard data area

KBIns           EQU     80h             ; bit masks for KB_FLAG
KBCaps          EQU     40h
KBNum           EQU     20h
KBScroll        EQU     10h
KBAlt           EQU     8
KBCtl           EQU     4
KBLeft          EQU     2
KBRight         EQU     1

SCEnter         EQU     1Ch

CR              EQU     0Dh
LF              EQU     0Ah
TRUE            EQU     -1
FALSE           EQU     0

                PAGE
;--------------------------------------------------------------------------
;
; RAM-resident routines
;
;--------------------------------------------------------------------------

RESIDENT_GROUP  GROUP   RESIDENT_TEXT,RESIDENT_DATA,RESIDENT_STACK
RESIDENT_TEXT   SEGMENT byte public 'CODE'
                ASSUME  cs:RESIDENT_GROUP,ds:RESIDENT_GROUP

;--------------------------------------------------------------------------
; System verification routines
;--------------------------------------------------------------------------

VerifyDOSState  PROC    near            ; Returns:    carry flag set if MS-
                                        ; DOS is busy

                push    ds              ; preserve these registers
                push    bx
                push    ax

                lds     bx,cs:ErrorModeAddr
                mov     ah,[bx]         ; AH = ErrorMode flag

                lds     bx,cs:InDOSAddr
                mov     al,[bx]         ; AL = InDOS flag

                xor     bx,bx           ; BH = 00H, BL = 00H
                cmp     bl,cs:InISR28   ; carry flag set if INT 28H
                                        ; handler is running
                rcl     bl,01h          ; BL = 01H if INT 28H handler is
                                        ; running

                cmp     bx,ax           ; carry flag zero if AH = 00H
                                        ; and AL <= BL

                pop     ax              ; restore registers
                pop     bx
                pop     ds
                ret

VerifyDOSState  ENDP


VerifyIntState  PROC    near            ; Returns:    carry flag set if
                                        ; hardware or ROM BIOS unstable

                push    ax              ; preserve AX

; Verify hardware interrupt status by interrogating Intel 8259A
;  Programmable Interrupt Controller

                mov     ax,00001011b    ; AH = 0
                                        ; AL = 0CW3 for Intel 8259A
                                        ; (RR = 1, RIS = 1)
                out     20h,al          ; request 8259A's in-service
                                        ; register
                jmp     short L10       ; wait a few cycles
L10:            in      al,20h          ; AL = hardware interrupts
                                        ; currently being serviced
                                        ; (bit = 1 if in-service)
                cmp     ah,al
                jc      L11             ; exit if any hardware interrupts
                                        ; still being serviced

; Verify status of ROM BIOS interrupt handlers

                xor     al,al           ; AL = 00H

                cmp     al,cs:InISR5
                jc      L11             ; exit if currently in INT 05H
                                        ; handler

                cmp     al,cs:InISR9
                jc      L11             ; exit if currently in INT 09H
                                        ; handler

                cmp     al,cs:InISR10
                jc      L11             ; exit if currently in INT 10H
                                        ; handler

                cmp     al,cs:InISR13   ; set carry flag if currently in
                                        ; INT 13H handler

L11:            pop     ax              ; restore AX and return
                ret

VerifyIntState  ENDP


VerifyTSRState  PROC    near            ; Returns: carry flag set
                                        ; if TSR inactive

                rol     cs:HotFlag,1    ; carry flag set if (HotFlag =
                                        ; TRUE)
                cmc                     ; carry flag set if (HotFlag =
                                        ; FALSE)
                jc      L20             ; exit if no hot key

                ror     cs:ActiveTSR,1  ; carry flag set if (ActiveTSR =
                                        ; TRUE)
                jc      L20             ; exit if already active

                call    VerifyDOSState
                jc      L20             ; exit if MS-DOS unstable

                call    VerifyIntState  ; set carry flag if hardware or
                                        ; BIOS unstable

L20:            ret

VerifyTSRState  ENDP

                PAGE
;--------------------------------------------------------------------------
; System monitor routines
;--------------------------------------------------------------------------

ISR5            PROC    far             ; INT 05H handler (ROM BIOS print
                                        ; screen)

                inc     cs:InISR5       ; increment status flag
                pushf
                cli
                call    cs:PrevISR5     ; chain to previous INT 05H handler

                dec     cs:InISR5       ; decrement status flag
                iret

ISR5            ENDP


ISR8            PROC    far             ; INT 08H handler (timer tick,
                                        ; IRQ0)

                pushf
                cli
                call    cs:PrevISR8     ; chain to previous handler

                cmp     cs:InISR8,0
                jne     L31             ; exit if already in this handler

                inc     cs:InISR8       ; increment status flag

                sti                     ; interrupts are ok
                call    VerifyTSRState
                jc      L30             ; jump if TSR is inactive

                mov     byte ptr cs:ActiveTSR,TRUE
                call    TSRapp
                mov     byte ptr cs:ActiveTSR,FALSE

L30:            dec     cs:InISR8

L31:            iret

ISR8            ENDP


ISR9            PROC    far             ; INT 09H handler
                                        ; (keyboardinterrupt IRQ1)

                push    ds              ; preserve these registers
                push    ax
                push    bx

                push    cs
                pop     ds              ; DS -> RESIDENT_GROUP

                in      al,60h          ; AL = current scan code

                pushf                   ; simulate an INT
                cli
                call    ds:PrevISR9     ; let previous handler execute
                mov     ah,ds:InISR9    ; if already in this handler ..
                or      ah,ds:HotFlag   ; .. or currently processing hot
                                        ; key ..
                jnz     L43             ; .. jump to exit

                inc     ds:InISR9       ; increment status flag
                sti                     ; now interrupts are ok

; Check scan code sequence

                cmp     ds:HotSeqLen,0
                je      L40             ; jump if no hot sequence to match

                mov     bx,ds:HotIndex
                cmp     al,[bx+HotSequence]     ; test scan code sequence
                jne     L41             ; jump if no match

                inc     bx
                cmp     bx,ds:HotSeqLen
                jb      L42             ; jump if not last scan code to
                                        ; match

; Check shift-key state

L40:            push    ds
                mov     ax,40h
                mov     ds,ax           ; DS -> ROM BIOS data area
                mov     al,ds:[KB_FLAG] ; AH = ROM BIOS shift-key flags
                pop     ds

                and     al,ds:HotKBMask ; AL = flags AND "don't care" mask
                cmp     al,ds:HotKBFlag
                jne     L42             ; jump if shift state does not
                                        ; match

; Set flag when hot key is found

                mov     byte ptr ds:HotFlag,TRUE

L41:            xor     bx,bx           ; reinitialize index

L42:            mov     ds:HotIndex,bx  ; update index into sequence
                dec     ds:InISR9       ; decrement status flag

L43:            pop     bx              ; restore registers and exit
                pop     ax
                pop     ds
                iret

ISR9            ENDP
ISR10           PROC    far             ; INT 10H handler (ROM BIOS video
                                        ; I/O)

                inc     cs:InISR10      ; increment status flag

                pushf
                cli
                call    cs:PrevISR10    ; chain to previous INT 10H handler

                dec     cs:InISR10      ; decrement status flag
                iret

ISR10           ENDP


ISR13           PROC    far             ; INT 13H handler (ROM BIOS fixed
                                        ; disk I/O)

                inc     cs:InISR13      ; increment status flag

                pushf
                cli
                call    cs:PrevISR13    ; chain to previous INT 13H handler

                pushf                   ; preserve returned flags
                dec     cs:InISR13      ; decrement status flag
                popf                    ; restore flags register

                sti                     ; enable interrupts
                ret     2               ; simulate IRET without popping
                                        ; flags

ISR13           ENDP


ISR1B           PROC    far             ; INT 1BH trap (ROM BIOS Ctrl-
                                        ; Break)

                mov     byte ptr cs:Trap1B,TRUE
                iret

ISR1B           ENDP


ISR23           PROC    far             ; INT 23H trap (MS-DOS Ctrl-C)

                mov     byte ptr cs:Trap23,TRUE
                iret

ISR23           ENDP


ISR24           PROC    far             ; INT 24H trap (MS-DOS critical
                                        ; error)

                mov     byte ptr cs:Trap24,TRUE
                xor     al,al           ; AL = 00H (MS-DOS 2.x): ignore the
                                        ; error
                cmp     cs:MajorVersion,2
                je      L50

                mov     al,3            ; AL = 03H (MS-DOS 3.x):
                                        ; fail the MS-DOS call in which
                                        ; the critical error occurred

L50:            iret

ISR24           ENDP


ISR28           PROC    far             ; INT 28H handler
                                        ; (MS-DOS idle interrupt)

                pushf
                cli
                call    cs:PrevISR28    ; chain to previous INT 28H handler

                cmp     cs:InISR28,0
                jne     L61             ; exit if already inside this
                                        ; handler

                inc     cs:InISR28      ; increment status flag

                call    VerifyTSRState
                jc      L60             ; jump if TSR is inactive

                mov     byte ptr cs:ActiveTSR,TRUE
                call    TSRapp
                mov     byte ptr cs:ActiveTSR,FALSE

L60:            dec     cs:InISR28      ; decrement status flag

L61:            iret

ISR28           ENDP


ISR2F           PROC    far             ; INT 2FH handler
                                        ; (MS-DOS multiplex interrupt)
                                        ; Caller:  AH = handler ID
                                        ;          AL = function number
                                        ; Returns for function 0:  AL = 0FFH
                                        ; for all other functions:  nothing

                cmp     ah,MultiplexID
                je      L70             ; jump if this handler is requested

                jmp     cs:PrevISR2F    ; chain to previous INT 2FH handler
L70:            test    al,al
                jnz     MultiplexIRET   ; jump if reserved or undefined
                                        ; function

; Function 0:  get installed state

                mov     al,0FFh         ; AL = 0FFH (this handler is
                                        ; installed)

MultiplexIRET:  iret                    ; return from interrupt

ISR2F           ENDP

                PAGE
;
;
; AuxInt21--sets ErrorMode while executing INT 21H to force use of the
;       AuxStack instead of the IOStack.
;
;

AuxInt21        PROC    near            ; Caller:     registers for INT 21H
                                        ; Returns:    registers from
                                        ;             INT 21H
                push    ds
                push    bx
                lds     bx,ErrorModeAddr
                inc     byte ptr [bx]   ; ErrorMode is now nonzero
                pop     bx
                pop     ds

                int     21h             ; perform MS-DOS function

                push    ds
                push    bx
                lds     bx,ErrorModeAddr
                dec     byte ptr [bx]   ; restore ErrorMode
                pop     bx
                pop     ds
                ret

AuxInt21        ENDP


Int21v          PROC    near            ; perform INT 21H or AuxInt21,
                                        ; depending on MS-DOS version

                cmp     DOSVersion,30Ah
                jb      L80             ; jump if earlier than 3.1

                int     21h             ; versions 3.1 and later
                ret



L80:            call    AuxInt21        ; versions earlier than 3.1
                ret

Int21v          ENDP

                PAGE
;--------------------------------------------------------------------------
; RAM-resident application
;--------------------------------------------------------------------------

TSRapp          PROC    near

; Set up a safe stack

                push    ds              ; save previous DS on previous
                                        ; stack

                push    cs
                pop     ds              ; DS -> RESIDENT_GROUP

                mov     PrevSP,sp       ; save previous SS:SP
                mov     PrevSS,ss

                mov     ss,TSRSS        ; SS:SP -> RESIDENT_STACK
                mov     sp,TSRSP

                push    es              ; preserve remaining registers
                push    ax
                push    bx
                push    cx
                push    dx
                push    si
                push    di
                push    bp

                cld                     ; clear direction flag

; Set break and critical error traps

                mov     cx,NTrap
                mov     si,offset RESIDENT_GROUP:StartTrapList

L90:            lodsb                   ; AL = interrupt number
                                        ; DS:SI -> byte past interrupt
                                        ; number

                mov     byte ptr [si],FALSE     ; zero the trap flag

                push    ax              ; preserve AX
                mov     ah,35h          ; INT 21H function 35H
                                        ; (get interrupt vector)
                int     21h             ; ES:BX = previous interrupt vector
                mov     [si+1],bx       ; save offset and segment ..
                mov     [si+3],es       ;  .. of previous handler
                pop     ax              ; AL = interrupt number
                mov     dx,[si+5]       ; DS:DX -> this TSR's trap
                mov     ah,25h          ; INT 21H function 25H
                int     21h             ; (set interrupt vector)
                add     si,7            ; DS:SI -> next in list

                loop    L90

; Disable MS-DOS break checking during disk I/O

                mov     ax,3300h        ; AH = INT 21H function number
                                        ; AL = 00H (request current break
                                        ; state)
                int     21h             ; DL = current break state
                mov     PrevBreak,dl    ; preserve current state

                xor     dl,dl           ; DL = 00H (disable disk I/O break
                                        ; checking)
                mov     ax,3301h        ; AL = 01H (set break state)
                int     21h

; Preserve previous extended error information

                cmp     DOSVersion,30Ah
                jb      L91             ; jump if MS-DOS version earlier
                                        ; than 3.1

                push    ds              ; preserve DS
                xor     bx,bx           ; BX = 00H (required for function
                                        ; 59H)
                mov     ah,59h          ; INT 21H function 59H
                call    Int21v          ; (get extended error info)

                mov     cs:PrevExtErrDS,ds
                pop     ds
                mov     PrevExtErrAX,ax ; preserve error information
                mov     PrevExtErrBX,bx ; in data structure
                mov     PrevExtErrCX,cx
                mov     PrevExtErrDX,dx
                mov     PrevExtErrSI,si
                mov     PrevExtErrDI,di
                mov     PrevExtErrES,es

; Inform MS-DOS about current PSP

L91:            mov     ah,51h          ; INT 21H function 51H (get PSP
                                        ; address)
                call    Int21v          ; BX = foreground PSP

                mov     PrevPSP,bx      ; preserve previous PSP

                mov     bx,TSRPSP       ; BX = resident PSP
                mov     ah,50h          ; INT 21H function 50H (set PSP
                                        ; address)
                call    Int21v
; Inform MS-DOS about current DTA (not really necessary in this application
; because DTA is not used)

                mov     ah,2Fh          ; INT 21H function 2FH
                int     21h             ; (get DTA address) into ES:BX
                mov     PrevDTAoffs,bx
                mov     PrevDTAseg,es

                push    ds              ; preserve DS
                mov     ds,TSRPSP
                mov     dx,80h          ; DS:DX -> default DTA at PSP:0080H
                mov     ah,1Ah          ; INT 21H function 1AH
                int     21h             ; (set DTA address)
                pop     ds              ; restore DS

; Open a file, write to it, and close it

                mov     ax,0E07h        ; AH = INT 10H function number
                                        ; (Write Teletype)
                                        ; AL = 07H (bell character)
                int     10h             ; emit a beep

                mov     dx,offset RESIDENT_GROUP:SnapFile
                mov     ah,3Ch          ; INT 21H function 3CH (create file
                                        ; handle)
                mov     cx,0            ; file attribute
                int     21h
                jc      L94             ; jump if file not opened

                push    ax              ; push file handle
                mov     ah,0Fh          ; INT 10H function 0FH (get video
                                        ; status)
                int     10h             ; AL = video mode number
                                        ; AH = number of character columns
                pop     bx              ; BX = file handle

                cmp     ah,80
                jne     L93             ; jump if not 80-column mode

                mov     dx,0B800h       ; DX = color video buffer segment
                cmp     al,3
                jbe     L92             ; jump if color alphanumeric mode

                cmp     al,7
                jne     L93             ; jump if not monochrome mode

                mov     dx,0B000h       ; DX = monochrome video buffer
                                        ; segment

L92:            push    ds
                mov     ds,dx
                xor     dx,dx           ; DS:DX -> start of video buffer
                mov     cx,80*25*2      ; CX = number of bytes to write
                mov     ah,40h          ; INT 21H function 40H (write file)
                int     21h
                pop     ds
L93:            mov     ah,3Eh          ; INT 21H function 3EH (close file)
                int     21h

                mov     ax,0E07h        ; emit another beep
                int     10h

; Restore previous DTA

L94:            push    ds              ; preserve DS
                lds     dx,PrevDTA      ; DS:DX -> previous DTA
                mov     ah,1Ah          ; INT 21H function 1AH (set DTA
                                        ; address)
                int     21h
                pop     ds

; Restore previous PSP

                mov     bx,PrevPSP      ; BX = previous PSP
                mov     ah,50h          ; INT 21H function 50H
                call    Int21v          ; (set PSP address)

; Restore previous extended error information

                mov     ax,DOSVersion
                cmp     ax,30Ah
                jb      L95             ; jump if MS-DOS version earlier
                                        ; than 3.1
                cmp     ax,0A00h
                jae     L95             ; jump if MS OS/2-DOS 3.x box

                mov     dx,offset RESIDENT_GROUP:PrevExtErrInfo
                mov     ax,5D0Ah
                int     21h             ; (restore extended error
                                        ; information)

; Restore previous MS-DOS break checking

L95:            mov     dl,PrevBreak    ; DL = previous state
                mov     ax,3301h
                int     21h

; Restore previous break and critical error traps

                mov     cx,NTrap
                mov     si,offset RESIDENT_GROUP:StartTrapList
                push    ds              ; preserve DS

L96:            lods    byte ptr cs:[si] ; AL = interrupt number
                                        ; ES:SI -> byte past interrupt
                                        ; number

                lds     dx,cs:[si+1]    ; DS:DX -> previous handler
                mov     ah,25h          ; INT 21H function 25H
                int     21h             ; (set interrupt vector)
                add     si,7            ; DS:SI -> next in list
                loop    L96
                pop     ds              ; restore DS

; Restore all registers

                pop     bp
                pop     di
                pop     si
                pop     dx
                pop     cx
                pop     bx
                pop     ax
                pop     es

                mov     ss,PrevSS       ; SS:SP -> previous stack
                mov     sp,PrevSP
                pop     ds              ; restore previous DS

; Finally, reset status flag and return

                mov     byte ptr cs:HotFlag,FALSE
                ret

TSRapp          ENDP


RESIDENT_TEXT   ENDS


RESIDENT_DATA   SEGMENT word public 'DATA'

ErrorModeAddr   DD      ?               ; address of MS-DOS ErrorMode flag
InDOSAddr       DD      ?               ; address of MS-DOS InDOS flag

NISR            DW      (EndISRList-StartISRList)/8 ; number of
                                        ; installed ISRs

StartISRList    DB      05h             ; INT number
InISR5          DB      FALSE           ; flag
PrevISR5        DD      ?               ; address of previous handler
                DW      offset RESIDENT_GROUP:ISR5

                DB      08h
InISR8          DB      FALSE
PrevISR8        DD      ?
                DW      offset RESIDENT_GROUP:ISR8

                DB      09h
InISR9          DB      FALSE
PrevISR9        DD      ?
                DW      offset RESIDENT_GROUP:ISR9

                DB      10h
InISR10         DB      FALSE
PrevISR10       DD      ?
                DW      offset RESIDENT_GROUP:ISR10
                DB      13h
InISR13         DB      FALSE
PrevISR13       DD      ?
                DW      offset RESIDENT_GROUP:ISR13

                DB      28h
InISR28         DB      FALSE
PrevISR28       DD      ?
                DW      offset RESIDENT_GROUP:ISR28

                DB      2Fh
InISR2F         DB      FALSE
PrevISR2F       DD      ?
                DW      offset RESIDENT_GROUP:ISR2F

EndISRList      LABEL   BYTE


TSRPSP          DW      ?               ; resident PSP
TSRSP           DW      TSRStackSize    ; resident SS:SP
TSRSS           DW      seg RESIDENT_STACK
PrevPSP         DW      ?               ; previous PSP
PrevSP          DW      ?               ; previous SS:SP
PrevSS          DW      ?

HotIndex        DW      0               ; index of next scan code in
                                        ; sequence
HotSeqLen       DW      EndHotSeq-HotSequence   ; length of hot-key
                                                ; sequence

HotSequence     DB      SCEnter         ; hot sequence of scan codes
EndHotSeq       LABEL   BYTE

HotKBFlag       DB      KBAlt           ; hot value of ROM BIOS KB_FLAG
HotKBMask       DB      (KBIns OR KBCaps OR KBNum OR KBScroll) XOR 0FFh
HotFlag         DB      FALSE

ActiveTSR       DB      FALSE

DOSVersion      LABEL   WORD
                DB      ?               ; minor version number
MajorVersion    DB      ?               ; major version number

; The following data is used by the TSR application:

NTrap           DW      (EndTrapList-StartTrapList)/8   ; number of traps

StartTrapList   DB      1Bh
Trap1B          DB      FALSE
PrevISR1B       DD      ?
                DW      offset RESIDENT_GROUP:ISR1B

                DB      23h
Trap23          DB      FALSE
PrevISR23       DD      ?
                DW      offset RESIDENT_GROUP:ISR23

                DB      24h
Trap24          DB      FALSE
PrevISR24       DD      ?
                DW      offset RESIDENT_GROUP:ISR24

EndTrapList     LABEL   BYTE

PrevBreak       DB      ?               ; previous break-checking flag

PrevDTA         LABEL   DWORD           ; previous DTA address
PrevDTAoffs     DW      ?
PrevDTAseg      DW      ?

PrevExtErrInfo  LABEL   BYTE            ; previous extended error
                                        ; information
PrevExtErrAX    DW      ?
PrevExtErrBX    DW      ?
PrevExtErrCX    DW      ?
PrevExtErrDX    DW      ?
PrevExtErrSI    DW      ?
PrevExtErrDI    DW      ?
PrevExtErrDS    DW      ?
PrevExtErrES    DW      ?
                DW      3 dup(0)

SnapFile        DB      '\snap.img'     ; output filename in root directory

RESIDENT_DATA   ENDS


RESIDENT_STACK  SEGMENT word stack 'STACK'

                DB      TSRStackSize dup(?)

RESIDENT_STACK  ENDS

                PAGE
;--------------------------------------------------------------------------
;
; Transient installation routines
;
;--------------------------------------------------------------------------

TRANSIENT_TEXT  SEGMENT para public 'TCODE'
                ASSUME cs:TRANSIENT_TEXT,ds:RESIDENT_DATA,ss:
                RESIDENT_STACK

InstallSnapTSR  PROC    far             ; At entry:  CS:IP ->
                                        ; InstallSnapTSR
                                        ;            SS:SP -> stack
                                        ;            DS,ES -> PSP


; Save PSP segment

                mov     ax,seg RESIDENT_DATA
                mov     ds,ax           ; DS  -> RESIDENT_DATA

                mov     TSRPSP,es       ; save PSP segment

; Check the MS-DOS version

                call    GetDOSVersion   ; AH = major version number
                                        ; AL = minor version number

; Verify that this TSR is not already installed
;
;       Before executing INT 2FH in MS-DOS versions 2.x, test whether
;       INT 2FH vector is in use. If so, abort if PRINT.COM is using it.
;
;       (Thus, in MS-DOS 2.x, if both this program and PRINT.COM are used,
;       this program should be made resident before PRINT.COM.)

                cmp     ah,2
                ja      L101            ; jump if version 3.0 or later

                mov     ax,352Fh        ; AH = INT 21H function number
                                        ; AL = interrupt number
                int     21h             ; ES:BX = INT 2FH vector

                mov     ax,es
                or      ax,bx           ; jump if current INT 2FH vector ..
                jnz     L100            ; .. is nonzero

                push    ds
                mov     ax,252Fh        ; AH = INT 21H function number
                                        ; AL = interrupt number
                mov     dx,seg RESIDENT_GROUP
                mov     ds,dx
                mov     dx,offset RESIDENT_GROUP:MultiplexIRET

                int     21h             ; point INT 2FH vector to IRET
                pop     ds
                jmp     short L103      ; jump to install this TSR

L100:           mov     ax,0FF00h       ; look for PRINT.COM:
                int     2Fh             ; if resident, AH = print queue
                                        ; length;
                                        ; otherwise, AH is unchanged

                cmp     ah,0FFh         ; if PRINT.COM is not resident ..
                je      L101            ; .. use multiplex interrupt

                mov     al,1
                call    FatalError      ; abort if PRINT.COM already
                                        ; installed
L101:           mov     ah,MultiplexID  ; AH = multiplex interrupt ID value
                xor     al,al           ; AL = 00H
                int     2Fh             ; multiplex interrupt

                test    al,al
                jz      L103            ; jump if ok to install

                cmp     al,0FFh
                jne     L102            ; jump if not already installed

                mov     al,2
                call    FatalError      ; already installed

L102:           mov     al,3
                call    FatalError      ; can't install

; Get addresses of InDOS and ErrorMode flags

L103:           call    GetDOSFlags

; Install this TSR's interrupt handlers

                push    es              ; preserve PSP segment

                mov     cx,NISR
                mov     si,offset StartISRList

L104:           lodsb                   ; AL = interrupt number
                                        ; DS:SI -> byte past interrupt
                                        ; number
                push    ax              ; preserve AX
                mov     ah,35h          ; INT 21H function 35H
                int     21h             ; ES:BX = previous interrupt vector
                mov     [si+1],bx       ; save offset and segment ..
                mov     [si+3],es       ; .. of previous handler

                pop     ax              ; AL = interrupt number
                push    ds              ; preserve DS
                mov     dx,[si+5]
                mov     bx,seg RESIDENT_GROUP
                mov     ds,bx           ; DS:DX -> this TSR's handler
                mov     ah,25h          ; INT 21H function 25H
                int     21h             ; (set interrupt vector)
                pop     ds              ; restore DS
                add     si,7            ; DS:SI -> next in list
                loop    L104

; Free the environment

                pop     es              ; ES = PSP segment
                push    es              ; preserve PSP segment
                mov     es,es:[2Ch]     ; ES = segment of environment
                mov     ah,49h          ; INT 21H function 49H
                int     21h             ; (free memory block)

; Terminate and stay resident

                pop     ax              ; AX = PSP segment
                mov     dx,cs           ; DX = paragraph address of start
                                        ; of transient portion (end of
                                        ; resident portion)
                sub     dx,ax           ; DX = size of resident portion

                mov     ax,3100h        ; AH = INT 21H function number
                                        ; AL = 00H (return code)
                int     21h

InstallSnapTSR  ENDP


GetDOSVersion   PROC    near            ; Caller:   DS = seg RESIDENT_DATA
                                        ;           ES = PSP
                                        ; Returns:  AH = major version
                                        ;           AL = minor version
                ASSUME  ds:RESIDENT_DATA

                mov     ah,30h          ; INT 21H function 30H:
                                        ; (get MS-DOS version)
                int     21h
                cmp     al,2
                jb      L110            ; jump if versions 1.x

                xchg    ah,al           ; AH = major version
                                        ; AL = minor version
                mov     DOSVersion,ax   ; save with major version in
                                        ; high-order byte
                ret

L110:           mov     al,00h
                call    FatalError      ; abort if versions 1.x

GetDOSVersion   ENDP
GetDOSFlags     PROC    near            ; Caller:   DS = seg
                                        ;           RESIDENT_DATA
                                        ; Returns:  InDOSAddr -> InDOS
                                        ;           ErrorModeAddr ->
                                        ;           ErrorMode
                                        ; Destroys: AX,BX,CX,DI
                ASSUME  ds:RESIDENT_DATA

; Get InDOS address from MS-DOS

                push    es

                mov     ah,34h          ; INT 21H function number
                int     21h             ; ES:BX - InDOS
                mov     word ptr InDOSAddr,bx
                mov     word ptr InDOSAddr+2,es

; Determine ErrorMode address

                mov     word ptr ErrorModeAddr+2,es   ; assume ErrorMode
                                                      ; is in the same
                                                      ; segment as InDOS

                mov     ax,DOSVersion
                cmp     ax,30Ah
                jb      L120            ; jump if MS-DOS version earlier
                                        ; than 3.1 ..
                cmp     ax,0A00h
                jae     L120            ; .. or MS OS/2-DOS 3.x box

                dec     bx              ; in MS-DOS 3.1 and later,
                                        ; ErrorMode
                mov     word ptr ErrorModeAddr,bx       ; is just before
                                                        ; InDOS
                jmp     short L125

L120:                                   ; scan MS-DOS segment for ErrorMode

                mov     cx,0FFFFh       ; CX = maximum number of bytes to
                                        ; scan
                xor     di,di           ; ES:DI -> start of MS-DOS segment

L121:           mov     ax,word ptr cs:LF2  ; AX = opcode for INT 28H

L122:           repne   scasb           ; scan for first byte of fragment
                jne     L126            ; jump if not found

                cmp     ah,es:[di]              ; inspect second byte of
                                                ; opcode
                jne     L122                    ; jump if not INT 28H

                mov     ax,word ptr cs:LF1 + 1  ; AX = opcode for CMP
                cmp     ax,es:[di][LF1-LF2]
                jne     L123                    ; jump if opcode not CMP

                mov     ax,es:[di][(LF1-LF2)+2] ; AX = offset of ErrorMode
                jmp     short L124              ; in DOS segment

L123:           mov     ax,word ptr cs:LF3 + 1  ; AX = opcode for TEST
                cmp     ax,es:[di][LF3-LF4]
                jne     L121                    ; jump if opcode not TEST

                mov     ax,es:[di][(LF3-LF4)+2] ; AX = offset of ErrorMode

L124:           mov     word ptr ErrorModeAddr,ax

L125:           pop     es
                ret
; Come here if address of ErrorMode not found

L126:           mov     al,04h
                call    FatalError


; Code fragments for scanning for ErrorMode flag

LFnear          LABEL   near            ; dummy labels for addressing
LFbyte          LABEL   byte
LFword          LABEL   word
                                        ; MS-DOS versions earlier than 3.1
LF1:            cmp     ss:LFbyte,0     ; CMP ErrorMode,0
                jne     LFnear
LF2:            int     28h
                                        ; MS-DOS versions 3.1 and later
LF3:            test    ss:LFbyte,0FFh  ; TEST ErrorMode,0FFH
                jne     LFnear
                push    ss:LFword
LF4:            int     28h

GetDOSFlags     ENDP

FatalError      PROC    near            ; Caller:   AL = message number
                                        ;           ES = PSP
                ASSUME  ds:TRANSIENT_DATA

                push    ax              ; save message number on stack

                mov     bx,seg TRANSIENT_DATA
                mov     ds,bx

; Display the requested message

                mov     bx,offset MessageTable
                xor     ah,ah           ; AX = message number
                shl     ax,1            ; AX = offset into MessageTable
                add     bx,ax           ; DS:BX -> address of message
                mov     dx,[bx]         ; DS:DX -> message
                mov     ah,09h          ; INT 21H function 09H (display
                                        ; string)
                int     21h             ; display error message

                pop     ax              ; AL = message number
                or      al,al
                jz      L130            ; jump if message number is zero
                                        ; (MS-DOS versions 1.x)

; Terminate (MS-DOS 2.x and later)

                mov     ah,4Ch          ; INT 21H function 4CH
                int     21h             ; (terminate process with return
                                        ; code)


; Terminate (MS-DOS 1.x)

L130            PROC    far

                push    es              ; push PSP:0000H
                xor     ax,ax
                push    ax
                ret                     ; far return (jump to PSP:0000H)

L130            ENDP

FatalError      ENDP


TRANSIENT_TEXT  ENDS

                PAGE
;
;
; Transient data segment
;
;

TRANSIENT_DATA  SEGMENT word public 'DATA'

MessageTable    DW   Message0        ; MS-DOS version error
                DW   Message1        ; PRINT.COM found in MS-DOS 2.x
                DW   Message2        ; already installed
                DW   Message3        ; can't install
                DW   Message4        ; can't find flag

Message0        DB   CR,LF,'TSR requires MS-DOS 2.0 or later
                     version',CR,LF,'$'
Message1        DB   CR,LF,'Can''t install TSR:
                     PRINT.COM active',CR,LF,'$'
Message2        DB   CR,LF,'This TSR is already installed',CR,LF,'$'
Message3        DB   CR,LF,'Can''t install this TSR',CR,LF,'$'
Message4        DB   CR,LF,'Unable to locate MS-DOS
                     ErrorMode flag',CR,LF,'$'

TRANSIENT_DATA  ENDS

                END     InstallSnapTSR



───────────────────────────────────────────────────────────────────────────


Figure 12-2.
INT24.ASM, a replacement Interrupt 24H handler.


        name    int24
        title   INT24 Critical Error Handler

;
; INT24.ASM -- Replacement critical error handler
; by Ray Duncan, September 1987
;

cr      equ     0dh             ; ASCII carriage return
lf      equ     0ah             ; ASCII linefeed

DGROUP  group   _DATA

_DATA   segment word public 'DATA'

save24  dd      0               ; previous contents of Int 24H
                                ; critical error handler vector
                                ; prompt message used by
                                ; critical error handler
prompt  db      cr,lf,'Critical Error Occurred: '
        db      'Abort, Retry, Ignore, Fail? $'

keys    db      'aArRiIfF'      ; possible user response keys
keys_len equ    $-keys          ; (both cases of each allowed)

codes   db      2,2,1,1,0,0,3,3 ; codes returned to MS-DOS kernel
                                ; for corresponding response keys

_DATA   ends


_TEXT   segment word public 'CODE'

        assume  cs:_TEXT,ds:DGROUP

        public  get24
get24   proc    near            ; set Int 24H vector to point
                                ; to new critical error handler

        push    ds              ; save segment registers
        push    es

        mov     ax,3524h        ; get address of previous
        int     21h             ; INT 24H handler and save it

        mov     word ptr save24,bx
        mov     word ptr save24+2,es

        push    cs              ; set DS:DX to point to
        pop     ds              ; new INT 24H handler
        mov     dx,offset _TEXT:int24
        mov     ax,2524h        ; then call MS-DOS to
        int     21h             ; set the INT 24H vector

        pop     es              ; restore segment registers
        pop     ds
        ret                     ; and return to caller

get24   endp


        public  res24
res24   proc    near            ; restore original contents
                                ; of Int 24H vector

        push    ds              ; save our data segment
        lds     dx,save24       ; put address of old handler
        mov     ax,2524h        ; back into INT 24H vector
        int     21h

        pop     ds              ; restore data segment
        ret                     ; and return to caller

res24   endp

;
; This is the replacement critical error handler. It
; prompts the user for Abort, Retry, Ignore, or Fail and
; returns the appropriate code to the MS-DOS kernel.
;
int24   proc    far             ; entered from MS-DOS kernel

        push    bx              ; save registers
        push    cx
        push    dx
        push    si
        push    di
        push    bp
        push    ds
        push    es

int24a: mov     ax,DGROUP       ; display prompt for user
        mov     ds,ax           ; using function 09H (print string
        mov     es,ax           ; terminated by $ character)
        mov     dx,offset prompt
        mov     ah,09h
        int     21h

        mov     ah,01h          ; get user's response
        int     21h             ; function 01H = read one character

        mov     di,offset keys  ; look up code for response key
        mov     cx,keys_len
        cld
        repne scasb
        jnz     int24a          ; prompt again if bad response

                                ; set AL = action code for MS-DOS
                                ; according to key that was entered:
                                ; 0 = ignore, 1 = retry, 2 = abort,
                                ; 3 = fail
        mov     al,[di+keys_len-1]

        pop     es              ; restore registers
        pop     ds
        pop     bp
        pop     di
        pop     si
        pop     dx
        pop     cx
        pop     bx
        iret                    ; exit critical error handler

int24   endp

_TEXT   ends

        end



───────────────────────────────────────────────────────────────────────────


Figure 13-5.
The Divide by Zero replacement handler, DIVZERO.ASM. This code is specific
to 80286 and 80386 microprocessors. (See Appendix M: 8086/8088 Software
Compatibility Issues.)


        name    divzero
        title   'DIVZERO - Interrupt 00H Handler'
;
; DIVZERO.ASM: Demonstration Interrupt 00H Handler
;
; To assemble, link, and convert to COM file:
;
;       C>MASM DIVZERO;  <Enter>
;       C>LINK DIVZERO;  <Enter>
;       C>EXE2BIN DIVZERO.EXE DIVZERO.COM  <Enter>
;       C>DEL DIVZERO.EXE  <Enter>
;

cr      equ     0dh             ; ASCII carriage return
lf      equ     0ah             ; ASCII linefeed
eos     equ     '$'             ; end of string marker

_TEXT   segment word public 'CODE'

        assume  cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT

        org     100h

entry:  jmp     start           ; skip over data area

intmsg  db      'Divide by Zero Occurred!',cr,lf,eos

divmsg  db      'Dividing '     ; message used by demo
par1    db      '0000h'         ; dividend goes here
        db      ' by '
par2    db      '00h'           ; divisor goes here
        db      ' equals '
par3    db      '00h'           ; quotient here
        db      ' remainder '
par4    db      '00h'           ; and remainder here
        db      cr,lf,eos

oldint0 dd      ?               ; save old Int 00H vector

intflag db      0               ; nonzero if divide by
                                ; zero interrupt occurred

oldip   dw      0               ; save old IP value


;
; The routine 'int0' is the actual divide by zero
; interrupt handler.  It gains control whenever a
; divide by zero or overflow occurs.  Its action
; is to set a flag and then increment the instruction
; pointer saved on the stack so that the failing
; divide will not be reexecuted after the IRET.
;
; In this particular case we can call MS-DOS to
; display a message during interrupt handling
; because the application triggers the interrupt
; intentionally. Thus, it is known that MS-DOS or
; other interrupt handlers are not in control
; at the point of interrupt.
;

int0:   pop     cs:oldip        ; capture instruction pointer

        push    ax
        push    bx
        push    cx
        push    dx
        push    di
        push    si
        push    ds
        push    es

        push    cs              ; set DS = CS
        pop     ds

        mov     ah,09h          ; print error message
        mov     dx,offset _TEXT:intmsg
        int     21h

        add     oldip,2         ; bypass instruction causing
                                ; divide by zero error

        mov     intflag,1       ; set divide by 0 flag

        pop     es              ; restore all registers
        pop     ds
        pop     si
        pop     di
        pop     dx
        pop     cx
        pop     bx
        pop     ax

        push    cs:oldip        ; restore instruction pointer

        iret                    ; return from interrupt


;
; The code beginning at 'start' is the application
; program.  It alters the vector for Interrupt 00H to
; point to the new handler, carries out some divide
; operations (including one that will trigger an
; interrupt) for demonstration purposes, restores
; the original contents of the Interrupt 00H vector,
; and then terminates.
;

start:  mov     ax,3500h        ; get current contents
        int     21h             ; of Int 00H vector

                                ; save segment:offset
                                ; of previous Int 00H handler
        mov     word ptr oldint0,bx
        mov     word ptr oldint0+2,es

                                ; install new handler...
        mov     dx,offset int0  ; DS:DX = handler address
        mov     ax,2500h        ; call MS-DOS to set
        int     21h             ; Int 00H vector

                                ; now our handler is active,
                                ; carry out some test divides.

        mov     ax,20h          ; test divide
        mov     bx,1            ; divide by 1
        call    divide

        mov     ax,1234h        ; test divide
        mov     bx,5eh          ; divide by 5EH
        call    divide

        mov     ax,5678h        ; test divide
        mov     bx,7fh          ; divide by 127
        call    divide

        mov     ax,20h          ; test divide
        mov     bx,0            ; divide by 0
        call    divide          ; (triggers interrupt)

                                ; demonstration complete,
                                ; restore old handler

        lds     dx,oldint0      ; DS:DX = handler address
        mov     ax,2500h        ; call MS-DOS to set
        int     21h             ; Int 00H vector

        mov     ax,4c00h        ; final exit to MS-DOS
        int     21h             ; with return code = 0

;
; The routine 'divide' carries out a trial division,
; displaying the arguments and the results.  It is
; called with AX = dividend and BL = divisor.
;

divide  proc    near

        push    ax              ; save arguments
        push    bx

        mov     di,offset par1  ; convert dividend to
        call    wtoa            ; ASCII for display

        mov     ax,bx           ; convert divisor to
        mov     di,offset par2  ; ASCII for display
        call    btoa

        pop     bx              ; restore arguments
        pop     ax

        div     bl              ; perform the division
        cmp     intflag,0       ; divide by zero detected?
        jne     nodiv           ; yes, skip display

        push    ax              ; no, convert quotient to
        mov     di,offset par3  ; ASCII for display
        call    btoa

        pop     ax              ; convert remainder to
        xchg    ah,al           ; ASCII for display
        mov     di,offset par4
        call    btoa

        mov     ah,09h          ; show arguments, results
        mov     dx,offset divmsg
        int     21h

nodiv:  mov     intflag,0       ; clear divide by 0 flag
        ret                     ; and return to caller

divide  endp



wtoa    proc    near            ; convert word to hex ASCII
                                ; call with AX = binary value
                                ;           DI = addr for string
                                ; returns AX, CX, DI destroyed

        push    ax              ; save original value
        mov     al,ah
        call    btoa            ; convert upper byte
        add     di,2            ; increment output address
        pop     ax
        call    btoa            ; convert lower byte
        ret                     ; return to caller

wtoa    endp



btoa    proc    near            ; convert byte to hex ASCII
                                ; call with AL = binary value
                                ;           DI = addr to store string
                                ; returns AX, CX destroyed

        mov     ah,al           ; save lower nibble
        mov     cx,4            ; shift right 4 positions
        shr     al,cl           ; to get upper nibble
        call    ascii           ; convert 4 bits to ASCII
        mov     [di],al         ; store in output string
        mov     al,ah           ; get back lower nibble

        and     al,0fh          ; blank out upper one
        call    ascii           ; convert 4 bits to ASCII
        mov     [di+1],al       ; store in output string
        ret                     ; back to caller

btoa    endp



ascii   proc    near            ; convert AL bits 0-3 to
                                ; ASCII {0...9,A...F}
        add     al,'0'          ; and return digit in AL
        cmp     al,'9'
        jle     ascii2
        add     al,'A'-'9'-1    ; "fudge factor" for A-F
ascii2: ret                     ; return to caller

ascii   endp

_TEXT   ends

        end     entry



───────────────────────────────────────────────────────────────────────────


Figure 14-1.
Assembly-language template for a character-oriented filter
(file PROTOC.ASM).


        name    protoc
        title   'PROTOC.ASM --- template character filter'
;
; PROTOC.ASM: a template for a character-oriented filter.
;
; Ray Duncan, June 1987
;

stdin   equ     0               ; standard input
stdout  equ     1               ; standard output
stderr  equ     2               ; standard error

cr      equ     0dh             ; ASCII carriage return
lf      equ     0ah             ; ASCII linefeed

DGROUP  group   _DATA,STACK     ; 'automatic data group'


_TEXT   segment byte public 'CODE'

        assume  cs:_TEXT,ds:DGROUP,ss:STACK

main    proc    far             ; entry point from MS-DOS

        mov     ax,DGROUP       ; set DS = our data segment
        mov     ds,ax

main1:                          ; read a character from standard input
        mov     dx,offset DGROUP:char   ; address to place character
        mov     cx,1            ; length to read = 1
        mov     bx,stdin        ; handle for standard input
        mov     ah,3fh          ; function 3FH = read from file or device
        int     21h             ; transfer to MS-DOS
        jc      main3           ; error, terminate
        cmp     ax,1            ; any character read?
        jne     main2           ; end of file, terminate program

        call    translt         ; translate character if necessary

                                ; now write character to standard output
        mov     dx,offset DGROUP:char   ; address of character
        mov     cx,1            ; length to write = 1
        mov     bx,stdout       ; handle for standard output
        mov     ah,40h          ; function 40H = write to file or device
        int     21h             ; transfer to MS-DOS
        jc      main3           ; error, terminate
        cmp     ax,1            ; was character written?
        jne     main3           ; disk full, terminate program
        jmp     main1           ; go process another character

main2:  mov     ax,4c00h        ; end of file reached, terminate
        int     21h             ; program with return code = 0

main3:  mov     ax,4c01h        ; error or disk full, terminate
        int     21h             ; program with return code = 1

main    endp                    ; end of main procedure

;
; Perform any necessary translation on character from input,
; stored in 'char'.  Template action: leave character unchanged.
;
translt proc    near

        ret                     ; template action: do nothing

translt endp

_TEXT   ends


_DATA   segment word public 'DATA'

char    db      0               ; temporary storage for input character

_DATA   ends


STACK   segment para stack 'STACK'

        dw      64 dup (?)

STACK   ends


        end     main            ; defines program entry point



───────────────────────────────────────────────────────────────────────────


Figure 14-2.
C template for a character-oriented filter (file PROTOC.C).


/*
        PROTOC.C: a template for a character-oriented filter.

        Ray Duncan, June 1987
*/

#include <stdio.h>

main(argc,argv)
int argc;
char *argv[];
{       char ch;

        while ( (ch=getchar())!=EOF )   /* read a character */
        {       ch=translate(ch);       /* translate it if necessary */
                putchar(ch);            /* write the character */
        }
        exit(0);                        /* terminate at end of file */
}

/*
        Perform any necessary translation on character from
        input file.  Template action just returns same character.
*/

int translate(ch)
char ch;
{       return (ch);
}


───────────────────────────────────────────────────────────────────────────

Figure 14-3.
Assembly-language template for a line-oriented filter (file PROTOL.ASM).


        name    protol
        title   'PROTOL.ASM --- template line filter'
;
; PROTOL.ASM:  a template for a line-oriented filter.
;
; Ray Duncan, June 1987
;

stdin   equ     0               ; standard input
stdout  equ     1               ; standard output
stderr  equ     2               ; standard error

cr      equ     0dh             ; ASCII carriage return
lf      equ     0ah             ; ASCII linefeed


DGROUP  group   _DATA,STACK     ; 'automatic data group'


_TEXT   segment byte public 'CODE'

        assume  cs:_TEXT,ds:DGROUP,es:DGROUP,ss:STACK

main    proc    far             ; entry point from MS-DOS

        mov     ax,DGROUP       ; set DS = ES = our data segment
        mov     ds,ax
        mov     es,ax

main1:                          ; read a line from standard input
        mov     dx,offset DGROUP:input  ; address to place data
        mov     cx,256          ; max length to read = 256
        mov     bx,stdin        ; handle for standard input
        mov     ah,3fh          ; function 3FH = read from file or device
        int     21h             ; transfer to MS-DOS
        jc      main3           ; if error, terminate
        or      ax,ax           ; any characters read?
        jz      main2           ; end of file, terminate program

        call    translt         ; translate line if necessary
        or      ax,ax           ; anything to output after translation?
        jz      main1           ; no, get next line

                                ; now write line to standard output
        mov     dx,offset DGROUP:output ; address of data
        mov     cx,ax           ; length to write
        mov     bx,stdout       ; handle for standard output
        mov     ah,40h          ; function 40H = write to file or device
        int     21h             ; transfer to MS-DOS
        jc      main3           ; if error, terminate

        cmp     ax,cx           ; was entire line written?
        jne     main3           ; disk full, terminate program
        jmp     main1           ; go process another line

main2:  mov     ax,4c00h        ; end of file reached, terminate
        int     21h             ; program with return code = 0

main3:  mov     ax,4c01h        ; error or disk full, terminate
        int     21h             ; program with return code = 1

main    endp                    ; end of main procedure

;
; Perform any necessary translation on line stored in
; 'input' buffer, leaving result in 'output' buffer.
;
; Call with:    AX = length of data in 'input' buffer.
;
; Return:       AX = length to write to standard output.
;
; Action of template routine is just to copy the line.
;
translt proc    near

                                ; just copy line from input to output
        mov     si,offset DGROUP:input
        mov     di,offset DGROUP:output
        mov     cx,ax
        rep movsb
        ret                     ; return length in AX unchanged

translt endp


_TEXT   ends


_DATA   segment word public 'DATA'

input   db      256 dup (?)     ; storage for input line
output  db      256 dup (?)     ; storage for output line

_DATA   ends


STACK   segment para stack 'STACK'

        dw      64 dup (?)

STACK   ends


        end     main            ; defines program entry point



───────────────────────────────────────────────────────────────────────────

Figure 14-4.
C template for a line-oriented filter (file PROTOL.C).


/*
        PROTOL.C: a template for a line-oriented filter.

        Ray Duncan, June 1987.
*/

#include <stdio.h>

static char input[256];                 /* buffer for input line */
static char output[256];                /* buffer for output line */

main(argc,argv)
int argc;
char *argv[];
{       while( gets(input) != NULL ) /* get a line from input stream */
                                     /* perform any necessary translation
                                           and possibly write result */
        {       if (translate()) puts(output);
        }
        exit(0);                      /* terminate at end of file */
}

/*
        Perform any necessary translation on input line, leaving
        the resulting text in output buffer.  Value of function
        is 'true' if output buffer should be written to standard output
        by main routine, 'false' if nothing should be written.
*/

translate()
{       strcpy(output,input);         /* template action is copy input */
        return(1);                    /* line and return true flag */
}


───────────────────────────────────────────────────────────────────────────


Figure 14-5.
Assembly-language source code for the LC filter (file LC.ASM).


        name    lc
        title   'LC.ASM --- lowercase filter'
;
; LC.ASM:       a simple character-oriented filter to translate
;               all uppercase {A-Z} to lowercase {a-z}.
;
; Ray Duncan, June 1987
;

stdin   equ     0               ; standard input
stdout  equ     1               ; standard output
stderr  equ     2               ; standard error

cr      equ     0dh             ; ASCII carriage return
lf      equ     0ah             ; ASCII linefeed


DGROUP  group   _DATA,STACK     ; 'automatic data group'


_TEXT   segment byte public 'CODE'

        assume  cs:_TEXT,ds:DGROUP,ss:STACK

main    proc    far             ; entry point from MS-DOS

        mov     ax,DGROUP       ; set DS = our data segment
        mov     ds,ax

main1:                          ; read a character from standard input
        mov     dx,offset DGROUP:char   ; address to place character
        mov     cx,1            ; length to read = 1
        mov     bx,stdin        ; handle for standard input
        mov     ah,3fh          ; function 3FH = read from file or device
        int     21h             ; transfer to MS-DOS
        jc      main3           ; error, terminate
        cmp     ax,1            ; any character read?
        jne     main2           ; end of file, terminate program

        call    translt         ; translate character if necessary

                              ; now write character to standard output
        mov     dx,offset DGROUP:char   ; address of character
        mov     cx,1            ; length to write = 1
        mov     bx,stdout       ; handle for standard output
        mov     ah,40h          ; function 40H = write to file or device
        int     21h             ; transfer to MS-DOS
        jc      main3           ; error, terminate
        cmp     ax,1            ; was character written?
        jne     main3           ; disk full, terminate program
        jmp     main1           ; go process another character

main2:  mov     ax,4c00h        ; end of file reached, terminate
        int     21h             ; program with return code = 0

main3:  mov     ax,4c01h        ; error or disk full, terminate
        int     21h             ; program with return code = 1

main    endp                    ; end of main procedure

;
; Translate uppercase {A-Z} characters to corresponding
; lowercase characters {a-z}.  Leave other characters unchanged.
;
translt proc    near

        cmp     byte ptr char,'A'
        jb      transx
        cmp     byte ptr char,'Z'
        ja      transx
        add     byte ptr char,'a'-'A'
transx: ret

translt endp


_TEXT   ends


_DATA   segment word public 'DATA'

char    db      0               ; temporary storage for input character

_DATA   ends


STACK   segment para stack 'STACK'

        dw      64 dup (?)

STACK   ends


        end     main            ; defines program entry point



───────────────────────────────────────────────────────────────────────────


Figure 14-6.
C source code for the LC filter (file LC.C).


/*
        LC:     a simple character-oriented filter to translate
                all uppercase {A-Z} to lowercase {a-z} characters.

        Usage:  LC [< source] [> destination]

        Ray Duncan, June 1987

*/

#include <stdio.h>

main(argc,argv)
int argc;
char *argv[];
{       char ch;
                                        /* read a character */
        while ( (ch=getchar() ) != EOF )
        {       ch=translate(ch);       /* perform any necessary
                                           character translation */
                putchar(ch);            /* then write character */
        }
        exit(0);                        /* terminate at end of file */
}

/*
        Translate characters A-Z to lowercase equivalents
*/

int translate(ch)
char ch;
{       if (ch >= 'A' && ch <= 'Z') ch += 'a'-'A';
        return (ch);
}



───────────────────────────────────────────────────────────────────────────


Figure 14-7.
C source code for a new FIND filter (file FIND.C).


/*
        FIND.C          Searches text stream for a string.

        Usage:          FIND "pattern" [< source] [> destination]

        by Ray Duncan, June 1987

*/

#include <stdio.h>

#define TAB     '\x09'                  /* ASCII tab character (^I) */
#define BLANK   '\x20'                  /* ASCII space character */

#define TAB_WIDTH 8                     /* columns per tab stop */

static char input[256];                 /* buffer for line from input */
static char output[256];                /* buffer for line to output */
static char pattern[256];               /* buffer for search pattern */

main(argc,argv)
int argc;
char *argv[];
{       int line=0;                     /* initialize line variable */

        if ( argc < 2 )                 /* was search pattern supplied? */
        {       puts("find: missing pattern.");
                exit(1);                /* abort if not */
        }
        strcpy(pattern,argv[1]);        /* save copy of string to find */
        strupr(pattern);                /* fold it to uppercase */
        while( gets(input) != NULL )    /* read a line from input */
        {       line++;                 /* count lines */
                strcpy(output,input);   /* save copy of input string */
                strupr(input);          /* fold input to uppercase */
                                        /* if line contains pattern */
                if( strstr(input,pattern) )
                                        /* write it to standard output */
                        writeline(line,output);
        }
        exit(0);                        /* terminate at end of file */
}

/*
        WRITELINE: Write line number and text to standard output,
        expanding any tab characters to stops defined by TAB_WIDTH.
*/

writeline(line,p)
int line;
char *p;
{       int i=0;                       /* index to original line text */
        int col=0;                     /* actual output column counter */
        printf("\n%4d: ",line);        /* write line number */
        while( p[i]!=NULL )            /* while end of line not reached */
        {       if(p[i]==TAB)          /* if current char=tab, expand it */
                {       do putchar(BLANK);
                        while((++col % TAB_WIDTH) != 0);
                }
                else                    /* otherwise just send character */
                {       putchar(p[i]);
                        col++;          /* count columns */
                }
                i++;                    /* advance through output line */
        }
}



───────────────────────────────────────────────────────────────────────────


Figure 14-8.
Assembly-language source code demonstrating use of a filter as a child
process. This code redirects the standard input and standard output handles
to files, invokes the EXEC function (Interrupt 21H Function 4BH) to run the
SORT.EXE program, and then restores the original meaning of the standard
input and standard output handles (file EXECSORT.ASM).


        name    execsort
        title   'EXECSORT --- demonstrate EXEC of filter'
        .sall
;
; EXECSORT.ASM --- demonstration of use of EXEC to run the SORT
; filter as a child process, redirecting its input and output.
; This program requires the files SORT.EXE and MYFILE.DAT in
; the current drive and directory.
;
; Ray Duncan, June 1987
;

stdin   equ     0                       ; standard input
stdout  equ     1                       ; standard output
stderr  equ     2                       ; standard error

stksize equ     128                     ; size of stack

cr      equ     0dh                     ; ASCII carriage return
lf      equ     0ah                     ; ASCII linefeed

jerr    macro   target                  ;; Macro to test carry flag
        local   notset                  ;; and jump if flag set.
        jnc     notset                  ;; Uses JMP DISP16 to avoid
        jmp     target                  ;; branch out of range errors
notset:
        endm


DGROUP  group   _DATA,_STACK            ; 'automatic data group'

_TEXT   segment byte public 'CODE'      ; executable code segment

        assume  cs:_TEXT,ds:DGROUP,ss:_STACK


stk_seg dw      ?                       ; original SS contents
stk_ptr dw      ?                       ; original SP contents


main    proc    far                     ; entry point from MS-DOS

        mov     ax,DGROUP               ; set DS = our data segment
        mov     ds,ax

                                        ; now give back extra memory so
                                        ; child SORT has somewhere
                                        ; to run...
        mov     ax,es                   ; let AX = segment of PSP base
        mov     bx,ss                   ; and BX = segment of stack base
        sub     bx,ax                   ; reserve seg stack - seg psp
        add     bx,stksize/16           ; plus paragraphs of stack
        mov     ah,4ah                  ; fxn 4AH = modify memory block
        int     21h                     ; transfer to MS-DOS
        jerr    main1                   ; jump if resize block failed

                                        ; prepare stdin and stdout
                                        ; handles for child SORT process

        mov     bx,stdin                ; dup the handle for stdin
        mov     ah,45h
        int     21h                     ; transfer to MS-DOS
        jerr    main1                   ; jump if dup failed
        mov     oldin,ax                ; save dup'd handle

        mov     dx,offset DGROUP:infile ; now open the input file
        mov     ax,3d00h                ; mode = read-only
        int     21h                     ; transfer to MS-DOS
        jerr    main1                   ; jump if open failed

        mov     bx,ax                   ; force stdin handle to
        mov     cx,stdin                ; track the input file handle
        mov     ah,46h
        int     21h                     ; transfer to MS-DOS
        jerr    main1                   ; jump if force dup failed

        mov     bx,stdout               ; dup the handle for stdout
        mov     ah,45h
        int     21h                     ; transfer to MS-DOS
        jerr    main1                   ; jump if dup failed
        mov     oldout,ax               ; save dup'd handle

        mov     dx,offset dGROUP:outfile ; now create the output file
        mov     cx,0                    ; normal attribute
        mov     ah,3ch
        int     21h                     ; transfer to MS-DOS
        jerr    main1                   ; jump if create failed

        mov     bx,ax                   ; force stdout handle to
        mov     cx,stdout               ; track the output file handle
        mov     ah,46h
        int     21h                     ; transfer to MS-DOS
        jerr    main1                   ; jump if force dup failed

                                        ; now EXEC the child SORT,
                                        ; which will inherit redirected
                                        ; stdin and stdout handles

        push    ds                      ; save EXECSORT's data segment
        mov     stk_seg,ss              ; save EXECSORT's stack pointer
        mov     stk_ptr,sp

        mov     ax,ds                   ; set ES = DS
        mov     es,ax
        mov     dx,offset DGROUP:cname  ; DS:DX = child pathname
        mov     bx,offset DGROUP:pars   ; EX:BX = parameter block
        mov     ax,4b00h                ; function 4BH, subfunction 00H
        int     21h                     ; transfer to MS-DOS

        cli                             ; (for bug in some early 8088s)
        mov     ss,stk_seg              ; restore execsort's stack pointer
        mov     sp,stk_ptr
        sti                             ; (for bug in some early 8088s)
        pop     ds                      ; restore DS = our data segment

        jerr    main1                   ; jump if EXEC failed

        mov     bx,oldin                ; restore original meaning of
        mov     cx,stdin                ; standard input handle for
        mov     ah,46h                  ; this process
        int     21h
        jerr    main1                   ; jump if force dup failed

        mov     bx,oldout               ; restore original meaning
        mov     cx,stdout               ; of standard output handle
        mov     ah,46h                  ; for this process
        int     21h
        jerr    main1                   ; jump if force dup failed

        mov     bx,oldin                ; close dup'd handle of
        mov     ah,3eh                  ; original stdin
        int     21h                     ; transfer to MS-DOS
        jerr    main1                   ; jump if close failed

        mov     bx,oldout               ; close dup'd handle of
        mov     ah,3eh                  ; original stdout
        int     21h                     ; transfer to MS-DOS
        jerr    main1                   ; jump if close failed

                                        ; display success message
        mov     dx,offset DGROUP:msg1   ; address of message
        mov     cx,msg1_len             ; message length
        mov     bx,stdout               ; handle for standard output
        mov     ah,40h                  ; fxn 40H = write file or device
        int     21h                     ; transfer to MS-DOS
        jerr    main1

        mov     ax,4c00h                ; no error, terminate program
        int     21h                     ; with return code = 0

main1:  mov     ax,4c01h                ; error, terminate program
        int     21h                     ; with return code = 1

main    endp                            ; end of main procedure

_TEXT   ends


_DATA   segment para public 'DATA'      ; static & variable data segment

infile  db      'MYFILE.DAT',0          ; input file for SORT filter
outfile db      'MYFILE.SRT',0          ; output file for SORT filter

oldin   dw      ?                       ; dup of old stdin handle
oldout  dw      ?                       ; dup of old stdout handle

cname   db      'SORT.EXE',0            ; pathname of child SORT process

pars    dw      0                       ; segment of environment block
                                        ; (0 = inherit parent's)
        dd      tail                    ; long address, command tail
        dd      -1                      ; long address, default FCB #1
                                        ; (-1 = none supplied)
        dd      -1                      ; long address, default FCB #2
                                        ; (-1 = none supplied)

tail    db      0,cr                    ; empty command tail for child

msg1    db      cr,lf,'SORT was executed as child.',cr,lf
msg1_len equ    $-msg1

_DATA   ends

_STACK  segment para stack 'STACK'

        db      stksize dup (?)

_STACK  ends


        end     main                    ; defines program entry point



───────────────────────────────────────────────────────────────────────────


Figure 15-14.
TEMPLATE.ASM, the source file for the TEMPLATE.SYS driver.


        name    template
        title   'TEMPLATE --- installable driver template'

;
; TEMPLATE.ASM:  A program skeleton for an installable
;                device driver (MS-DOS 2.0 or later)
;
; The driver command-code routines are stubs only and have
; no effect but to return a nonerror "Done" status.
;
; Ray Duncan, July 1987
;

_TEXT   segment byte public 'CODE'

        assume  cs:_TEXT,ds:_TEXT,es:NOTHING

        org     0

MaxCmd  equ     24              ; maximum allowed command code
                                ; 12 for MS-DOS 2.x
                                ; 16 for MS-DOS 3.0-3.1
                                ; 24 for MS-DOS 3.2-3.3

cr      equ     0dh             ; ASCII carriage return
lf      equ     0ah             ; ASCII linefeed
eom     equ     '$'             ; end-of-message signal


Header:                         ; device driver header
        dd      -1              ; link to next device driver
        dw      0c840h          ; device attribute word
        dw      Strat           ; "Strategy" routine entry point
        dw      Intr            ; "Interrupt" routine entry point
        db      'TEMPLATE'      ; logical device name


RHPtr   dd      ?               ; pointer to request header, passed
                                ; by MS-DOS kernel to Strategy routine

Dispatch:                       ; Interrupt routine command-code
                                ; dispatch table
        dw      Init            ;  0 = initialize driver
        dw      MediaChk        ;  1 = media check on block device
        dw      BuildBPB        ;  2 = build BIOS parameter block
        dw      IoctlRd         ;  3 = I/O control read
        dw      Read            ;  4 = read (input) from device
        dw      NdRead          ;  5 = nondestructive read
        dw      InpStat         ;  6 = return current input status
        dw      InpFlush        ;  7 = flush device input buffers
        dw      Write           ;  8 = write (output) to device
        dw      WriteVfy        ;  9 = write with verify
        dw      OutStat         ; 10 = return current output status
        dw      OutFlush        ; 11 = flush output buffers
        dw      IoctlWt         ; 12 = I/O control write
        dw      DevOpen         ; 13 = device open       (MS-DOS 3.0+)
        dw      DevClose        ; 14 = device close      (MS-DOS 3.0+)
        dw      RemMedia        ; 15 = removable media   (MS-DOS 3.0+)
        dw      OutBusy         ; 16 = output until busy (MS-DOS 3.0+)
        dw      Error           ; 17 = not used
        dw      Error           ; 18 = not used
        dw      GenIOCTL        ; 19 = generic IOCTL     (MS-DOS 3.2+)
        dw      Error           ; 20 = not used
        dw      Error           ; 21 = not used
        dw      Error           ; 22 = not used
        dw      GetLogDev       ; 23 = get logical device (MS-DOS 3.2+)
        dw      SetLogDev       ; 24 = set logical device (MS-DOS 3.2+)


Strat   proc    far             ; device driver Strategy routine,
                                ; called by MS-DOS kernel with
                                ; ES:BX = address of request header

                                ; save pointer to request header
        mov     word ptr cs:[RHPtr],bx
        mov     word ptr cs:[RHPtr+2],es

        ret                     ; back to MS-DOS kernel

Strat   endp


Intr    proc   far              ; device driver Interrupt routine,
                                ; called by MS-DOS kernel immediately
                                ; after call to Strategy routine

        push    ax              ; save general registers
        push    bx
        push    cx
        push    dx
        push    ds
        push    es
        push    di
        push    si
        push    bp

        push    cs              ; make local data addressable
        pop     ds              ; by setting DS = CS

        les     di,[RHPtr]      ; let ES:DI = request header

                                ; get BX = command code
        mov     bl,es:[di+2]
        xor     bh,bh
        cmp     bx,MaxCmd       ; make sure it's valid
        jle     Intr1           ; jump, function code is ok
        call    Error           ; set error bit, "Unknown Command" code
        jmp     Intr2

Intr1:  shl     bx,1            ; form index to dispatch table
                                ; and branch to command-code routine
        call    word ptr [bx+Dispatch]

        les     di,[RHPtr]      ; ES:DI = address of request header

Intr2:  or      ax,0100h        ; merge Done bit into status and
        mov     es:[di+3],ax    ; store status into request header

        pop     bp              ; restore general registers
        pop     si
        pop     di
        pop     es
        pop     ds
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret                     ; return to MS-DOS kernel


; Command-code routines are called by the Interrupt routine
; via the dispatch table with ES:DI pointing to the request
; header. Each routine should return AX = 00H if function was
; completed successfully or AX = 8000H + error code if
; function failed.


MediaChk proc   near            ; function 1 = Media Check

        xor     ax,ax
        ret

MediaChk endp

BuildBPB proc   near            ; function 2 = Build BPB

        xor     ax,ax
        ret

BuildBPB endp


IoctlRd proc    near            ; function 3 = I/O Control Read

        xor     ax,ax
        ret

IoctlRd endp


Read    proc    near            ; function 4 = Read (Input)

        xor     ax,ax
        ret

Read    endp


NdRead  proc    near            ; function 5 = Nondestructive Read

        xor     ax,ax
        ret

NdRead  endp


InpStat proc    near            ; function 6 = Input Status

        xor     ax,ax
        ret

InpStat endp


InpFlush proc   near            ; function 7 = Flush Input Buffers

        xor     ax,ax
        ret

InpFlush endp

Write   proc    near            ; function 8 = Write (Output)

        xor     ax,ax
        ret

Write   endp


WriteVfy proc   near            ; function 9 = Write with Verify

        xor     ax,ax
        ret

WriteVfy endp


OutStat proc    near            ; function 10 = Output Status

        xor     ax,ax
        ret

OutStat endp


OutFlush proc   near            ; function 11 = Flush Output Buffers

        xor     ax,ax
        ret

OutFlush endp


IoctlWt proc    near            ; function 12 = I/O Control Write

        xor     ax,ax
        ret

IoctlWt endp


DevOpen proc    near            ; function 13 = Device Open

        xor     ax,ax
        ret

DevOpen endp

DevClose proc   near            ; function 14 = Device Close

        xor     ax,ax
        ret

DevClose endp


RemMedia proc   near            ; function 15 = Removable Media

        xor     ax,ax
        ret

RemMedia endp


OutBusy proc    near            ; function 16 = Output Until Busy

        xor     ax,ax
        ret

OutBusy endp


GenIOCTL proc   near            ; function 19 = Generic IOCTL

        xor     ax,ax
        ret

GenIOCTL endp


GetLogDev proc  near            ; function 23 = Get Logical Device

        xor     ax,ax
        ret

GetLogDev endp


SetLogDev proc  near            ; function 24 = Set Logical Device

        xor     ax,ax
        ret

SetLogDev endp


Error   proc    near            ; bad command code in request header

        mov     ax,8003h        ; error bit + "Unknown Command" code
        ret

Error   endp


Init    proc    near            ; function 0 = initialize driver

        push    es              ; save address of request header
        push    di

        mov     ah,9            ; display driver sign-on message
        mov     dx,offset Ident
        int     21h

        pop     di              ; restore request header address
        pop     es

                                ; set address of free memory
                                ; above driver (break address)
        mov     word ptr es:[di+14],offset Init
        mov     word ptr es:[di+16],cs

        xor     ax,ax           ; return status
        ret

Init    endp

Ident   db      cr,lf,lf
        db      'TEMPLATE Example Device Driver'
        db      cr,lf,eom

Intr    endp


_TEXT   ends

        end



───────────────────────────────────────────────────────────────────────────


Figure 15-15.
TINYDISK.ASM, the source file for the TINYDISK.SYS driver.


        name    tinydisk
        title   TINYDISK example block-device driver

; TINYDISK.ASM --- 64 KB RAMdisk
;
; Ray Duncan, July 1987
; Example of a simple installable block-device driver.

_TEXT   segment public 'CODE'

        assume  cs:_TEXT,ds:_TEXT,es:_TEXT

        org     0

MaxCmd  equ     12              ; max driver command code
                                ; (no MS-DOS 3.x functions)

cr      equ     0dh             ; ASCII carriage return
lf      equ     0ah             ; ASCII linefeed
blank   equ     020h            ; ASCII space code
eom     equ     '$'             ; end-of-message signal

Secsize equ     512             ; bytes/sector, IBM-compatible media

                                ; device-driver header
Header  dd      -1              ; link to next driver in chain
        dw      0               ; device attribute word
        dw      Strat           ; "Strategy" routine entry point
        dw      Intr            ; "Interrupt" routine entry point
        db      1               ; number of units, this device
        db      7 dup (0)       ; reserved area (block-device drivers)

RHPtr   dd      ?               ; segment:offset of request header

Secseg  dw      ?               ; segment base of sector storage

Xfrsec  dw      0               ; current sector for transfer
Xfrcnt  dw      0               ; sectors successfully transferred
Xfrreq  dw      0               ; number of sectors requested
Xfraddr dd      0               ; working address for transfer

Array   dw      BPB             ; array of pointers to BPB
                                ; for each supported unit


Bootrec equ     $

        jmp     $               ; phony JMP at start of
        nop                     ; boot sector; this field
                                ; must be 3 bytes

        db      'MS   2.0'      ; OEM identity field

                                ; BIOS Parameter Block (BPB)
BPB     dw      Secsize         ; 00H - bytes per sector
        db      1               ; 02H - sectors per cluster
        dw      1               ; 03H - reserved sectors
        db      1               ; 05H - number of FATs
        dw      32              ; 06H - root directory entries
        dw      128             ; 08H - sectors = 64 KB/secsize
        db      0f8h            ; 0AH - media descriptor
        dw      1               ; 0BH - sectors per FAT

Bootrec_len equ $-Bootrec


Strat   proc    far             ; RAMdisk strategy routine

                                ; save address of request header
        mov     word ptr cs:RHPtr,bx
        mov     word ptr cs:[RHPtr+2],es
        ret                     ; back to MS-DOS kernel

Strat   endp

Intr    proc    far             ; RAMdisk interrupt routine

        push    ax              ; save general registers
        push    bx
        push    cx
        push    dx
        push    ds
        push    es
        push    di
        push    si
        push    bp

        mov     ax,cs           ; make local data addressable
        mov     ds,ax

        les     di,[RHPtr]      ; ES:DI = request header

        mov     bl,es:[di+2]    ; get command code
        xor     bh,bh
        cmp     bx,MaxCmd       ; make sure it's valid
        jle     Intr1           ; jump, function code is ok
        mov     ax,8003h        ; set Error bit and
        jmp     Intr3           ; "Unknown Command" error code

Intr1:  shl     bx,1            ; form index to dispatch table and
                                ; branch to command-code routine
        call    word ptr [bx+Dispatch]
                                ; should return AX = status

        les     di,[RHPtr]      ; restore ES:DI = request header

Intr3:  or      ax,0100h        ; merge Done bit into status and store
        mov     es:[di+3],ax    ; status into request header

Intr4:  pop     bp              ; restore general registers
        pop     si
        pop     di
        pop     es
        pop     ds
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret                     ; return to MS-DOS kernel

Intr    endp

Dispatch:                       ; command-code dispatch table
                                ; all command-code routines are
                                ; entered with ES:DI pointing
                                ; to request header and return
                                ; the operation status in AX
        dw      Init            ;  0 = initialize driver
        dw      MediaChk        ;  1 = media check on block device
        dw      BuildBPB        ;  2 = build BIOS parameter block
        dw      Dummy           ;  3 = I/O control read
        dw      Read            ;  4 = read (input) from device
        dw      Dummy           ;  5 = nondestructive read
        dw      Dummy           ;  6 = return current input status
        dw      Dummy           ;  7 = flush device input buffers
        dw      Write           ;  8 = write (output) to device
        dw      Write           ;  9 = write with verify
        dw      Dummy           ; 10 = return current output status
        dw      Dummy           ; 11 = flush output buffers
        dw      Dummy           ; 12 = I/O control write


MediaChk proc  near             ; command code 1 = Media Check

                                ; return "not changed" code
        mov     byte ptr es:[di+0eh],1
        xor     ax,ax           ; and success status
        ret

MediaChk endp


BuildBPB proc  near             ; command code 2 = Build BPB

                                ; put BPB address in request header
        mov     word ptr es:[di+12h],offset BPB
        mov     word ptr es:[di+14h],cs
        xor     ax,ax           ; return success status
        ret

BuildBPB endp


Read    proc    near            ; command code 4 = Read (Input)

        call    Setup           ; set up transfer variables

Read1:  mov     ax,Xfrcnt       ; done with all sectors yet?
        cmp     ax,Xfrreq
        je      Read2           ; jump if transfer completed
        mov     ax,Xfrsec       ; get next sector number
        call    Mapsec          ; and map it
        mov     ax,es
        mov     si,di
        les     di,Xfraddr      ; ES:DI = requester's buffer
        mov     ds,ax           ; DS:SI = RAMdisk address
        mov     cx,Secsize      ; transfer logical sector from
        cld                     ; RAMdisk to requestor
        rep movsb
        push    cs              ; restore local addressing
        pop     ds
        inc     Xfrsec          ; advance sector number
                                ; advance transfer address
        add     word ptr Xfraddr,Secsize
        inc     Xfrcnt          ; count sectors transferred
        jmp     Read1

Read2:                          ; all sectors transferred
        xor     ax,ax           ; return success status
        les     di,RHPtr        ; put actual transfer count
        mov     bx,Xfrcnt       ; into request header
        mov     es:[di+12h],bx
        ret

Read    endp


Write   proc    near            ; command code 8 = Write (Output)
                                ; command code 9 = Write with Verify

        call    Setup           ; set up transfer variables

Write1: mov     ax,Xfrcnt       ; done with all sectors yet?
        cmp     ax,Xfrreq
        je      Write2          ; jump if transfer completed

        mov     ax,Xfrsec       ; get next sector number
        call    Mapsec          ; and map it
        lds     si,Xfraddr
        mov     cx,Secsize      ; transfer logical sector from
        cld                     ; requester to RAMdisk
        rep movsb
        push    cs              ; restore local addressing
        pop     ds
        inc     Xfrsec          ; advance sector number
                                ; advance transfer address
        add     word ptr Xfraddr,Secsize
        inc     Xfrcnt          ; count sectors transferred
        jmp     Write1

Write2:                         ; all sectors transferred
        xor     ax,ax           ; return success status
        les     di,RHPtr        ; put actual transfer count
        mov     bx,Xfrcnt       ; into request header
        mov     es:[di+12h],bx
        ret

Write   endp


Dummy   proc    near            ; called for unsupported functions

        xor     ax,ax           ; return success flag for all
        ret

Dummy   endp


Mapsec  proc    near            ; map sector number to memory address
                                ; call with AX = logical sector no.
                                ; return ES:DI = memory address

        mov     di,Secsize/16   ; paragraphs per sector
        mul     di              ; * logical sector number
        add     ax,Secseg       ; + segment base of sector storage
        mov     es,ax
        xor     di,di           ; now ES:DI points to sector
        ret

Mapsec  endp


Setup   proc    near            ; set up for read or write
                                ; call ES:DI = request header
                                ; extracts address, start, count

        push    es              ; save request header address
        push    di
        mov     ax,es:[di+14h]  ; starting sector number
        mov     Xfrsec,ax
        mov     ax,es:[di+12h]  ; sectors requested
        mov     Xfrreq,ax
        les     di,es:[di+0eh]  ; requester's buffer address
        mov     word ptr Xfraddr,di
        mov     word ptr Xfraddr+2,es
        mov     Xfrcnt,0        ; initialize sectors transferred count
        pop     di              ; restore request header address
        pop     es
        ret

Setup   endp

Init    proc    near            ; command code 0 = Initialize driver
                                ; on entry ES:DI = request header

        mov     ax,cs           ; calculate segment base for sector
        add     ax,Driver_len   ; storage and save it
        mov     Secseg,ax
        add     ax,1000h        ; add 1000H paras (64 KB) and
        mov     es:[di+10h],ax  ; set address of free memory
        mov     word ptr es:[di+0eh],0

        call    Format          ; format the RAMdisk

        call    Signon          ; display driver identification

        les     di,cs:RHPtr     ; restore ES:DI = request header
                                ; set logical units = 1
        mov     byte ptr es:[di+0dh],1
                                ; set address of BPB array
        mov     word ptr es:[di+12h],offset Array
        mov     word ptr es:[di+14h],cs

        xor     ax,ax           ; return success status
        ret

Init    endp


Format  proc    near            ; format the RAMdisk area

        mov     es,Secseg       ; first zero out RAMdisk
        xor     di,di
        mov     cx,8000h        ; 32 K words = 64 KB
        xor     ax,ax
        cld
        rep stosw

        mov     ax,0            ; get address of logical
        call    Mapsec          ; sector zero
        mov     si,offset Bootrec
        mov     cx,Bootrec_len
        rep movsb               ; and copy boot record to it

        mov     ax,word ptr BPB+3
        call    Mapsec          ; get address of 1st FAT sector
        mov     al,byte ptr BPB+0ah
        mov     es:[di],al      ; put media ID byte into it
        mov     word ptr es:[di+1],-1

        mov     ax,word ptr BPB+3
        add     ax,word ptr BPB+0bh
        call    Mapsec          ; get address of 1st directory sector
        mov     si,offset Volname
        mov     cx,Volname_len
        rep movsb               ; copy volume label to it

        ret                     ; done with formatting

Format  endp


Signon  proc    near            ; driver identification message

        les     di,RHPtr        ; let ES:DI = request header
        mov     al,es:[di+22]   ; get drive code from header,
        add     al,'A'          ; convert it to ASCII, and
        mov     drive,al        ; store into sign-on message

        mov     ah,30h          ; get MS-DOS version
        int     21h
        cmp     al,2
        ja      Signon1         ; jump if version 3.0 or later
        mov     Ident1,eom      ; version 2.x, don't print drive

Signon1:                        ; print sign-on message
        mov     ah,09H          ; Function 09H = print string
        mov     dx,offset Ident ; DS:DX = address of message
        int     21h             ; transfer to MS-DOS

        ret                     ; back to caller

Signon  endp


Ident   db      cr,lf,lf        ; driver sign-on message
        db      'TINYDISK 64 KB RAMdisk'
        db      cr,lf
Ident1  db      'RAMdisk will be drive '
Drive   db      'X:'
        db      cr,lf,eom


Volname db      'DOSREF_DISK'   ; volume label for RAMdisk
        db      08h             ; attribute byte
        db      10 dup (0)      ; reserved area
        dw      0               ; time = 00:00
        dw      0f01h           ; date = August 1, 1987
        db      6 dup (0)       ; reserved area

Volname_len equ $-volname

Driver_len dw (($-header)/16)+1 ; driver size in paragraphs

_TEXT   ends

        end



───────────────────────────────────────────────────────────────────────────


Figure 17-11.
The SAMPLE.C source code.


/* SAMPLE.C -- Demonstration Windows Program */

#include <windows.h>
#include "sample.h"

long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;

int PASCAL WinMain (hInstance, hPrevInstance, lpszCmdLine, nCmdShow)
     HANDLE      hInstance, hPrevInstance ;
     LPSTR       lpszCmdLine ;
     int         nCmdShow ;
     {
     WNDCLASS    wndclass ;
     HWND        hWnd ;
     MSG         msg ;
     static char szAppName [] = "Sample" ;

               /*---------------------------*/
               /* Register the Window Class */
               /*---------------------------*/

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW  CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = NULL ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

               /*----------------------------------*/
               /* Create the window and display it */
               /*----------------------------------*/

     hWnd = CreateWindow (szAppName, "Demonstration Windows Program",
                          WS_OVERLAPPEDWINDOW,
                          (int) CW_USEDEFAULT, 0,
                          (int) CW_USEDEFAULT, 0,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hWnd, nCmdShow) ;
     UpdateWindow (hWnd) ;

                /*----------------------------------------------*/
                /* Stay in message loop until a WM_QUIT message */
                /*----------------------------------------------*/

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

long FAR PASCAL WndProc (hWnd, iMessage, wParam, lParam)
     HWND         hWnd ;
     unsigned     iMessage ;
     WORD         wParam ;
     LONG         lParam ;
     {
     PAINTSTRUCT  ps ;
     HFONT        hFont ;
     HMENU        hMenu ;
     static short xClient, yClient, nCurrentFont = IDM_SCRIPT ;
     static BYTE  cFamily [] = { FF_SCRIPT, FF_MODERN, FF_ROMAN } ;
     static char  *szFace [] = { "Script",  "Modern",  "Roman"  } ;

     switch (iMessage)
          {

                    /*---------------------------------------------*/
                    /* WM_COMMAND message: Change checkmarked font */
                    /*---------------------------------------------*/

          case WM_COMMAND:
               hMenu = GetMenu (hWnd) ;
               CheckMenuItem (hMenu, nCurrentFont, MF_UNCHECKED) ;
               nCurrentFont = wParam ;
               CheckMenuItem (hMenu, nCurrentFont, MF_CHECKED) ;
               InvalidateRect (hWnd, NULL, TRUE) ;
               break ;
                    /*--------------------------------------------*/
                    /* WM_SIZE message: Save dimensions of window */
                    /*--------------------------------------------*/

          case WM_SIZE:
               xClient = LOWORD (lParam) ;
               yClient = HIWORD (lParam) ;
               break ;

                    /*-----------------------------------------------*/
                    /* WM_PAINT message: Display "Windows" in Script */
                    /*-----------------------------------------------*/

          case WM_PAINT:
               BeginPaint (hWnd, &ps) ;

               hFont = CreateFont (yClient, xClient / 8,
                                   0, 0, 400, 0, 0, 0, OEM_CHARSET,
                                   OUT_STROKE_PRECIS, OUT_STROKE_PRECIS,
                                   DRAFT_QUALITY, (BYTE) VARIABLE_PITCH
                                   cFamily [nCurrentFont - IDM_SCRIPT],
                                   szFace  [nCurrentFont - IDM_SCRIPT]) ;

               hFont = SelectObject (ps.hdc, hFont) ;
               TextOut (ps.hdc, 0, 0, "Windows", 7) ;

               DeleteObject (SelectObject (ps.hdc, hFont)) ;
               EndPaint (hWnd, &ps) ;
               break ;

                    /*---------------------------------------*/
                    /* WM_DESTROY message: Post Quit message */
                    /*---------------------------------------*/

          case WM_DESTROY:
               PostQuitMessage (0) ;
               break ;

                    /*---------------------------------------*/
                    /* Other messages: Do default processing */
                    /*---------------------------------------*/

          default:
               return DefWindowProc (hWnd, iMessage, wParam, lParam) ;
          }
     return 0L ;
     }



───────────────────────────────────────────────────────────────────────────


Figure 18-1.
A routine to compute exponentials.


'  EXP.BAS -- COMPUTE EXPONENTIAL WITH INFINITE SERIES
'
' **********************************************************************
' *                                                                     *
' *  EXP                                                                *
' *                                                                     *
' *  This routine computes EXP(x) using the following infinite series:  *
' *                                                                     *
' *                       x    x^2   x^3   x^4   x^5                    *
' *         EXP(x) = 1 + --- + --- + --- + --- + --- + ...              *
' *                       1!    2!    3!    4!    5!                    *
' *                                                                     *
' *                                                                     *
' *  The program requests a value for x and a value for the convergence *
' *  criterion, C.  The program will continue evaluating the terms of   *
' *  the series until the difference between two terms is less than C.  *
' *                                                                     *
' *  The result of the calculation and the number of terms required to  *
' *  converge are printed.  The program will repeat until an x of 0 is  *
' *  entered.                                                           *
' *                                                                     *
' **********************************************************************

'
'  Initialize program variables
'
INITIALIZE:
        TERMS = 1
        FACT = 1
        LAST = 1.E35
        DELTA = 1.E34
        EX = 1
'
'  Input user data
'
        INPUT "Enter number:  "; X
        IF X = 0 THEN END
        INPUT "Enter convergence criterion (.0001 for 4 places):  "; C

'
'  Compute exponential until difference of last 2 terms is < C
'
        WHILE ABS(LAST - DELTA) >= C
            LAST = DELTA
            FACT = FACT * TERMS
            DELTA = X^TERMS / FACT
            EX = EX + DELTA
            TERMS = TERMS + 1
        WEND
'
'  Display answer and number of terms required to converge
'
        PRINT EX
        PRINT TERMS; "elements required to converge"
        PRINT

        GOTO INITIALIZE



───────────────────────────────────────────────────────────────────────────


Figure 18-3.
Corrected exponential calculation routine.


'  EXP.BAS -- COMPUTE EXPONENTIAL WITH INFINITE SERIES
'
' **********************************************************************
' *                                                                     *
' *  EXP                                                                *
' *                                                                     *
' *  This routine computes EXP(x) using the following infinite series:  *
' *                                                                     *
' *                       x    x^2   x^3   x^4   x^5                    *
' *         EXP(x) = 1 + --- + --- + --- + --- + --- + ...              *
' *                       1!    2!    3!    4!    5!                    *
' *                                                                     *
' *                                                                     *
' *  The program requests a value for x and a value for the convergence *
' *  criterion, C.  The program will continue evaluating the terms of   *
' *  the series until the amount added with a term is less than C.      *
' *                                                                     *
' *  The result of the calculation and the number of terms required to  *
' *  converge are printed.  The program will repeat until an x of 0 is  *
' *  entered.                                                           *
' *                                                                     *
' **********************************************************************

'
'  Initialize program variables
'
INITIALIZE:
        TERMS = 1
        FACT = 1
        DELTA = 1.E35
        EX = 1
'
'  Input user data
'
        INPUT "Enter number:  "; X
        IF X = 0 THEN END
        INPUT "Enter convergence criterion (.0001 for 4 places):  "; C

'
'  Compute exponential until difference of last 2 terms is < C



        WHILE DELTA > C
            FACT = FACT * TERMS
            DELTA = X^TERMS / FACT
            EX = EX + DELTA
            TERMS = TERMS + 1
        WEND

'
'  Display answer and number of terms required to converge
'
        PRINT EX
        PRINT TERMS; "elements required to converge"
        PRINT

        GOTO INITIALIZE



───────────────────────────────────────────────────────────────────────────


Figure 18-4.
Communications trace utility.


        TITLE   COMMSCOP -- COMMUNICATIONS TRACE UTILITY
; ***********************************************************************
; *                                                                     *
; *  COMMSCOP --                                                        *
; *     THIS PROGRAM MONITORS THE ACTIVITY ON A SPECIFIED COMM PORT     *
; *     AND PLACES A COPY OF ALL COMM ACTIVITY IN A RAM BUFFER. EACH   *
; *     ENTRY IN THE BUFFER IS TAGGED TO INDICATE WHETHER THE BYTE      *
; *     WAS SENT BY OR RECEIVED BY THE SYSTEM.                          *
; *                                                                     *
; *     COMMSCOP IS INSTALLED BY ENTERING                               *
; *                                                                     *
; *                     COMMSCOP                                        *
; *                                                                     *
; *     THIS WILL INSTALL COMMSCOP AND SET UP A 64K BUFFER TO BE USED   *
; *     FOR DATA LOGGING. REMEMBER THAT 2 BYTES ARE REQUIRED FOR       *
; *     EACH COMM BYTE, SO THE BUFFER IS ONLY 32K EVENTS LONG, OR ABOUT *
; *     30 SECONDS OF CONTINUOUS 9600 BAUD DATA. IN THE REAL WORLD,    *
; *     ASYNC DATA IS RARELY CONTINUOUS, SO THE BUFFER WILL PROBABLY    *
; *     HOLD MORE THAN 30 SECONDS WORTH OF DATA.                        *
; *                                                                     *
; *     WHEN INSTALLED, COMMSCOP INTERCEPTS ALL INT 14H CALLS. IF THE  *
; *     PROGRAM HAS BEEN ACTIVATED AND THE INT IS EITHER SEND OR RE-    *
; *     CEIVE DATA, A COPY OF THE DATA BYTE, PROPERLY TAGGED, IS PLACED *
; *     IN THE BUFFER. IN ANY CASE, DATA IS PASSED ON TO THE REAL      *
; *     INT 14H HANDLER.                                                *
; *                                                                     *
; *     COMMSCOP IS INVOKED BY ISSUING AN INT 60H CALL. THE INT HAS    *
; *     THE FOLLOWING CALLING SEQUENCE:                                 *
; *                                                                     *
; *             AH -- COMMAND                                           *
; *                   0 -- STOP TRACING, PLACE STOP MARK IN BUFFER      *
; *                   1 -- FLUSH BUFFER AND START TRACE                 *
; *                   2 -- RESUME TRACE                                 *
; *                   3 -- RETURN COMM BUFFER ADDRESSES                 *
; *             DX -- COMM PORT (ONLY USED WITH AH = 1 or 2)            *
; *                   0 -- COM1                                         *
; *                   1 -- COM2                                         *
; *                                                                     *
; *     THE FOLLOWING DATA IS RETURNED IN RESPONSE TO AH = 3:           *
; *                                                                     *
; *             CX -- BUFFER COUNT IN BYTES                             *
; *             DX -- SEGMENT ADDRESS OF THE START OF THE BUFFER        *
; *             BX -- OFFSET ADDRESS OF THE START OF THE BUFFER         *
; *                                                                     *
; *     THE COMM BUFFER IS FILLED WITH 2-BYTE DATA ENTRIES OF THE       *
; *     FOLLOWING FORM:                                                 *
; *                                                                     *
; *             BYTE 0 -- CONTROL                                       *
; *                  BIT 0 -- ON FOR RECEIVED DATA, OFF FOR TRANS.      *
; *                  BIT 7 -- STOP MARK -- INDICATES COLLECTION WAS     *
; *                              INTERRUPTED AND RESUMED.               *
; *             BYTE 1 -- 8-BIT DATA                                    *
; *                                                                     *
; ***********************************************************************

CSEG    SEGMENT
        ASSUME  CS:CSEG,DS:CSEG
        ORG     100H                    ;TO MAKE A COMM FILE

INITIALIZE:
        JMP     VECTOR_INIT             ;JUMP TO THE INITIALIZATION
                                        ; ROUTINE WHICH, TO SAVE SPACE,
                                        ; IS IN THE COMM BUFFER

;
;  SYSTEM VARIABLES
;
OLD_COMM_INT    DD      ?               ;ADDRESS OF REAL COMM INT
COUNT           DW      0               ;BUFFER COUNT
COMMSCOPE_INT   EQU     60H             ;COMMSCOPE CONTROL INT
STATUS          DB      0               ;PROCESSING STATUS
                                        ; 0 -- OFF
                                        ; 1 -- ON
PORT            DB      0               ;COMM PORT BEING TRACED
BUFPNTR         DW      VECTOR_INIT     ;NEXT BUFFER LOCATION

        SUBTTL  DATA INTERRUPT HANDLER
PAGE
; ***********************************************************************
; *                                                                     *
; *  COMMSCOPE                                                          *
; *     THIS PROCEDURE INTERCEPTS ALL INT 14H CALLS AND LOGS THE DATA   *
; *     IF APPROPRIATE.                                                 *
; *                                                                     *
; ***********************************************************************
COMMSCOPE       PROC    NEAR

        TEST    CS:STATUS,1             ;ARE WE ON?
        JZ      OLD_JUMP                ; NO, SIMPLY JUMP TO OLD HANDLER

        CMP     AH,00H                  ;SKIP SETUP CALLS
        JE      OLD_JUMP                ; .

        CMP     AH,03H                  ;SKIP STATUS REQUESTS
        JAE     OLD_JUMP                ; .

        CMP     AH,02H                  ;IS THIS A READ REQUEST?
        JE      GET_READ                ; YES, GO PROCESS

;
;  DATA WRITE REQUEST -- SAVE IF APPROPRIATE
;
        CMP     DL,CS:PORT              ;IS WRITE FOR PORT BEING TRACED?
        JNE     OLD_JUMP                ; NO, JUST PASS IT THROUGH

        PUSH    DS                      ;SAVE CALLER'S REGISTERS
        PUSH    BX                      ; .
        PUSH    CS                      ;SET UP DS FOR OUR PROGRAM
        POP     DS                      ; .
        MOV     BX,BUFPNTR              ;GET ADDR OF NEXT BUFFER LOC
        MOV     [BX],BYTE PTR 0         ;MARK AS TRANSMITTED BYTE
        MOV     [BX+1],AL               ;SAVE DATA IN BUFFER
        INC     COUNT                   ;INCREMENT BUFFER BYTE COUNT
        INC     COUNT                   ; .
        INC     BX                      ;POINT TO NEXT LOCATION
        INC     BX                      ; .
        MOV     BUFPNTR,BX              ;SAVE NEW POINTER
        JNZ     WRITE_DONE              ;ZERO MEANS BUFFER HAS WRAPPED

        MOV     STATUS,0                ;TURN COLLECTION OFF
WRITE_DONE:
        POP     BX                      ;RESTORE CALLER'S REGISTERS
        POP     DS                      ; .
        JMP     OLD_JUMP                ;PASS REQUEST ON TO BIOS ROUTINE
;
;  PROCESS A READ DATA REQUEST AND WRITE TO BUFFER IF APPROPRIATE
;
GET_READ:
        CMP     DL,CS:PORT              ;IS READ FOR PORT BEING TRACED?
        JNE     OLD_JUMP                ; NO, JUST PASS IT THROUGH

        PUSH    DS                      ;SAVE CALLER'S REGISTERS
        PUSH    BX                      ; .
        PUSH    CS                      ;SET UP DS FOR OUR PROGRAM
        POP     DS                      ; .

        PUSHF                           ;FAKE INT 14H CALL
        CLI                             ; .
        CALL    OLD_COMM_INT            ;PASS REQUEST ON TO BIOS
        TEST    AH,80H                  ;VALID READ?
        JNZ     READ_DONE               ; NO, SKIP BUFFER UPDATE
        MOV     BX,BUFPNTR              ;GET ADDR OF NEXT BUFFER LOC
        MOV     [BX],BYTE PTR 1         ;MARK AS RECEIVED BYTE
        MOV     [BX+1],AL               ;SAVE DATA IN BUFFER
        INC     COUNT                   ;INCREMENT BUFFER BYTE COUNT
        INC     COUNT                   ; .
        INC     BX                      ;POINT TO NEXT LOCATION
        INC     BX                      ; .
        MOV     BUFPNTR,BX              ;SAVE NEW POINTER
        JNZ     READ_DONE               ;ZERO MEANS BUFFER HAS WRAPPED

        MOV     STATUS,0                ;TURN COLLECTION OFF
READ_DONE:
        POP     BX                      ;RESTORE CALLER'S REGISTERS
        POP     DS                      ; .
        IRET

;
;  JUMP TO COMM BIOS ROUTINE
;
OLD_JUMP:
        JMP     CS:OLD_COMM_INT

COMMSCOPE ENDP

        SUBTTL  CONTROL INTERRUPT HANDLER
PAGE
; ***********************************************************************
; *                                                                     *
; *  CONTROL                                                            *
; *     THIS ROUTINE PROCESSES CONTROL REQUESTS.                        *
; *                                                                     *
; ***********************************************************************

CONTROL PROC    NEAR
        CMP     AH,00H                  ;STOP REQUEST?
        JNE     CNTL_START              ; NO, CHECK START
        PUSH    DS                      ;SAVE REGISTERS
        PUSH    BX                      ; .
        PUSH    CS                      ;SET DS FOR OUR ROUTINE
        POP     DS
        MOV     STATUS,0                ;TURN PROCESSING OFF
        MOV     BX,BUFPNTR              ;PLACE STOP MARK IN BUFFER
        MOV     [BX],BYTE PTR 80H       ; .
        MOV     [BX+1],BYTE PTR 0FFH    ; .
        INC     BX                      ;INCREMENT BUFFER POINTER
        INC     BX                      ; .
        MOV     BUFPNTR,BX              ; .
        INC     COUNT                   ;INCREMENT COUNT
        INC     COUNT                   ; .
        POP     BX                      ;RESTORE REGISTERS
        POP     DS                      ; .
        JMP     CONTROL_DONE

CNTL_START:
        CMP     AH,01H                  ;START REQUEST?
        JNE     CNTL_RESUME             ; NO, CHECK RESUME
        MOV     CS:PORT,DL              ;SAVE PORT TO TRACE
        MOV     CS:BUFPNTR,OFFSET VECTOR_INIT ;RESET BUFFER TO START
        MOV     CS:COUNT,0              ;ZERO COUNT
        MOV     CS:STATUS,1             ;START LOGGING
        JMP     CONTROL_DONE

CNTL_RESUME:
        CMP     AH,02H                  ;RESUME REQUEST?
        JNE     CNTL_STATUS             ; NO, CHECK STATUS
        CMP     CS:BUFPNTR,0            ;END OF BUFFER CONDITION?
        JE      CONTROL_DONE            ; YES, DO NOTHING
        MOV     CS:PORT,DL              ;SAVE PORT TO TRACE
        MOV     CS:STATUS,1             ;START LOGGING
        JMP     CONTROL_DONE

CNTL_STATUS:
        CMP     AH,03H                  ;RETURN STATUS REQUEST?
        JNE     CONTROL_DONE            ; NO, ERROR -- DO NOTHING
        MOV     CX,CS:COUNT             ;RETURN COUNT
        PUSH    CS                      ;RETURN SEGMENT ADDR OF BUFFER
        POP     DX                      ; .
        MOV     BX,OFFSET VECTOR_INIT   ;RETURN OFFSET ADDR OF BUFFER

CONTROL_DONE:
        IRET

CONTROL ENDP


        SUBTTL     INITIALIZE INTERRUPT VECTORS
PAGE
; ***********************************************************************
; *                                                                     *
; *  VECTOR_INIT                                                        *
; *     THIS PROCEDURE INITIALIZES THE INTERRUPT VECTORS AND THEN       *
; *     EXITS VIA THE MS-DOS TERMINATE-AND-STAY-RESIDENT FUNCTION.      *
; *     A BUFFER OF 64K IS RETAINED. THE FIRST AVAILABLE BYTE          *
; *     IN THE BUFFER IS THE OFFSET OF VECTOR_INIT.                     *
; *                                                                     *
; ***********************************************************************

        EVEN                            ;ASSURE BUFFER ON EVEN BOUNDARY
VECTOR_INIT     PROC     NEAR
;
;  GET ADDRESS OF COMM VECTOR (INT 14H)
;
        MOV     AH,35H



        MOV     AL,14H
        INT     21H
;
;  SAVE OLD COMM INT ADDRESS
;
        MOV     WORD PTR OLD_COMM_INT,BX
        MOV     AX,ES
        MOV     WORD PTR OLD_COMM_INT[2],AX
;
;  SET UP COMM INT TO POINT TO OUR ROUTINE
;
        MOV     DX,OFFSET COMMSCOPE
        MOV     AH,25H
        MOV     AL,14H
        INT     21H
;
;  INSTALL CONTROL ROUTINE INT
;
        MOV     DX,OFFSET CONTROL
        MOV     AH,25H
        MOV     AL,COMMSCOPE_INT
        INT     21H
;
;  SET LENGTH TO 64K, EXIT AND STAY RESIDENT
;
        MOV     AX,3100H          ;TERM AND STAY RES COMMAND
        MOV     DX,1000H          ;64K RESERVED
        INT     21H               ;DONE
VECTOR_INIT ENDP
CSEG    ENDS
        END     INITIALIZE



───────────────────────────────────────────────────────────────────────────


Figure 18-5.
A serial-trace control routine written in C.


/**********************************************************************
*                                                                      *
*  COMMSCMD                                                            *
*                                                                      *
*  This routine controls the COMMSCOP program that has been in-        *
*  stalled as a resident routine.  The operation performed is de-      *
*  termined by the command line.  The COMMSCMD program is invoked      *
*  as follows:                                                         *
*                                                                      *
*                COMMSCMD [[cmd][ port]]                               *
*                                                                      *
*  where cmd is the command to be executed                             *
*            STOP   -- stop trace                                      *
*            START  -- flush trace buffer and start trace              *
*            RESUME -- resume a stopped trace                          *
*        port is the COMM port to be traced (1=COM1, 2=COM2, etc.)     *
*                                                                      *
*  If cmd is omitted, STOP is assumed.  If port is omitted, 1 is       *
*  assumed.                                                            *
*                                                                      *
**********************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
#define COMMCMD 0x60

main(argc, argv)
int argc;
char *argv[];
{
    int cmd, port, result;
    static char commands[3] [10] = {"STOPPED", "STARTED", "RESUMED"};
    union REGS inregs, outregs;

    cmd = 0;
    port = 0;

    if (argc > 1)
        {
        if (0 == stricmp(argv[1], "STOP"))
            cmd = 0;
        else if (0 == stricmp(argv[1], "START"))
            cmd = 1;
        else if (0 == stricmp(argv[1], "RESUME"))
            cmd = 2;
        }

    if (argc == 3)
        {
        port = atoi(argv[2]);
        if (port > 0)
            port = port - 1;
        }

    inregs.h.ah = cmd;
    inregs.x.dx = port;
    result = int86(COMMCMD, &inregs, &outregs);


    printf("\nCommunications tracing %s for port COM%1d:\n",
            commands[cmd], port + 1);
}



───────────────────────────────────────────────────────────────────────────


Figure 18-6.
A QuickBASIC version of COMMSCMD.


' **********************************************************************
' *                                                                     *
' *  COMMSCMD                                                           *
' *                                                                     *
' *  This routine controls the COMMSCOP program that has been in-       *
' *  stalled as a resident routine.  The operation performed is de-     *
' *  termined by the command line.  The COMMSCMD program is invoked     *
' *  as follows:                                                        *
' *                                                                     *
' *             COMMSCMD [[cmd][,port]]                                 *
' *                                                                     *
' *  where cmd is the command to be executed                            *
' *             STOP   -- stop trace                                    *
' *             START  -- flush trace buffer and start trace            *
' *             RESUME -- resume a stopped trace                        *
' *        port is the COMM port to be traced (1=COM1 , 2=COM2 , etc.)    *
' *                                                                     *
' *  If cmd is omitted, STOP is assumed.  If port is omitted, 1 is      *
' *  assumed.                                                           *
' *                                                                     *
' **********************************************************************

        '
        '  Establish system constants and variables
        '
        DEFINT A-Z

        DIM INREG(7), OUTREG(7)         'Define register arrays
        RAX = 0                         'Establish values for 8086
        RBX = 1                         '  registers
        RCX = 2                         '  .
        RDX = 3                         '  .
        RBP = 4                         '  .
        RSI = 5                         '  .
        RDI = 6                         '  .
        RFL = 7                         '  .

        DIM TEXT$(2)

        TEXT$(0) = "STOPPED"
        TEXT$(1) = "STARTED"
        TEXT$(2) = "RESUMED"

        '
        '  Process command-line tail
        '
        C$ = COMMAND$                   'Get command-line data

        IF LEN(C$) = 0 THEN             'If no command line specified
            CMD = 0                     'Set CMD to STOP
            PORT = 0                    'Set PORT to COM1
            GOTO SENDCMD
        END IF

        COMMA = INSTR(C$, ", ")         'Extract operands
        IF COMMA = 0 THEN
            CMDTXT$ = C$
            PORT = 0
        ELSE
            CMDTXT$ = LEFT$(C$, COMMA - 1)
            PORT = VAL(MID$(C$, COMMA + 1)) - 1
        END IF

        IF PORT < 0 THEN PORT = 0

        IF CMDTXT$ = "STOP" THEN
            CMD = 0
        ELSEIF CMDTXT$ = "START" THEN
            CMD = 1
        ELSEIF CMDTXT$ = "RESUME" THEN
            CMD = 2
        ELSE
            CMD = 0
        END IF

        '
        '  Send command to COMMSCOP routine
        '
SENDCMD:
        INREG(RAX) = 256 * CMD
        INREG(RDX) = PORT
        CALL INT86(&H60, VARPTR(INREG(0)), VARPTR(OUTREG(0)))
        '
        '  Notify user that action is complete
        '
        PRINT : PRINT
        PRINT "Communications tracing "; TEXT$(CMD);
        IF CMD <> 0 THEN
            PRINT " for port COM"; MID$(STR$(PORT + 1), 2); ":"
        ELSE
            PRINT
        END IF

        END



───────────────────────────────────────────────────────────────────────────


Figure 18-7.
Formatted dump routine for serial-trace buffer.


' **********************************************************************
' *                                                                     *
' *  COMMDUMP                                                           *
' *                                                                     *
' *  This routine dumps the contents of the COMMSCOP trace buffer to    *
' *  the screen in a formatted manner.  Received data is shown in       *
' *  reverse video.  Where possible, the ASCII character for the byte   *
' *  is shown; otherwise a dot is shown.  The value of the byte is      *
' *  displayed in hex below the character.  Points where tracing was    *
' *  stopped are shown by a solid bar.                                  *
' *                                                                     *
' **********************************************************************

        '
        '  Establish system constants and variables
        '
        DEFINT A-Z

        DIM INREG(7), OUTREG(7)         'Define register arrays

        RAX = 0                         'Establish values for 8086
        RBX = 1                         ' registers
        RCX = 2                         '  .
        RDX = 3                         '  .
        RBP = 4                         '  .
        RSI = 5                         '  .
        RDI = 6                         '  .
        RFL = 7                         '  .

        '
        '  Interrogate COMMSCOP to obtain addresses and count of data in
        '  trace buffer
        '
        INREG(RAX) = &H0300             'Request address data and count
        CALL INT86(&H60, VARPTR(INREG(0)), VARPTR(OUTREG(0)))

        NUM = OUTREG(RCX)               'Number of bytes in buffer
        BUFSEG = OUTREG(RDX)            'Buffer segment address
        BUFOFF = OUTREG(RBX)            'Offset of buffer start

        IF NUM = 0 THEN END

        '
        '  Set screen up and display control data
        '
        CLS
        KEY OFF
        LOCATE 25, 1
        PRINT "NUM ="; NUM;"BUFSEG = "; HEX$(BUFSEG); " BUFOFF = ";
        PRINT HEX$(BUFOFF);
        LOCATE 4, 1
        PRINT STRING$(80,"-")
        DEF SEG = BUFSEG

        '
        '  Set up display control variables
        '
        DLINE = 1
        DCOL = 1
        DSHOWN = 0

        '
        '  Fetch and display each character in buffer
        '
        FOR I= BUFOFF TO BUFOFF+NUM-2 STEP 2
            STAT = PEEK(I)
            DAT = PEEK(I + 1)

            IF (STAT AND 1) = 0 THEN
                COLOR 7, 0
            ELSE
                COLOR 0, 7
            END IF

            RLINE = (DLINE-1) * 4 + 1
            IF (STAT AND &H80) = 0 THEN
                LOCATE RLINE, DCOL
                C$ = CHR$(DAT)
                IF DAT < 32 THEN C$ = "."
                PRINT C$;
                H$ = RIGHT$("00" + HEX$(DAT), 2)
                LOCATE RLINE + 1, DCOL
                PRINT LEFT$(H$, 1);
                LOCATE RLINE + 2, DCOL
                PRINT RIGHT$(H$, 1);
            ELSE
                LOCATE RLINE, DCOL
                PRINT CHR$(178);
                LOCATE RLINE + 1, DCOL
                PRINT CHR$(178);
                LOCATE RLINE + 2, DCOL
                PRINT CHR$(178);
            END IF

            DCOL = DCOL + 1
            IF DCOL > 80 THEN
                COLOR 7, 0
                DCOL = 1
                DLINE = DLINE + 1
                SHOWN = SHOWN + 1
                IF SHOWN = 6 THEN
                    LOCATE 25, 50
                    COLOR 0, 7
                    PRINT "ENTER ANY KEY TO CONTINUE:  ";
                    WHILE LEN(INKEY$) = 0
                    WEND
                    COLOR 7, 0
                    LOCATE 25, 50
                    PRINT SPACE$(29);
                    SHOWN = 0
                END IF
                IF DLINE > 6 THEN
                    LOCATE 24, 1
                    PRINT : PRINT : PRINT : PRINT
                    LOCATE 24, 1
                    PRINT STRING$(80, "-");
                    DLINE = 6
                ELSE
                    LOCATE DLINE * 4, 1
                    PRINT STRING$(80, "-");
                END IF
            END IF

        NEXT I

        END



───────────────────────────────────────────────────────────────────────────


Figure 18-9.
Incorrect serial test routine.


        TITLE TESTCOMM -- TEST COMMSCOP ROUTINE
; **********************************************************************
; *                                                                     *
; *  TESTCOMM                                                           *
; *     THIS ROUTINE PROVIDES DATA FOR THE COMMSCOP ROUTINE.  IT READS  *
; *     CHARACTERS FROM THE KEYBOARD AND WRITES THEM TO COM1 USING      *
; *     INT 14H.  DATA IS ALSO READ FROM INT 14H AND DISPLAYED ON THE   *
; *     SCREEN.  THE ROUTINE RETURNS TO MS-DOS WHEN Ctrl-C IS PRESSED   *
; *     ON THE KEYBOARD.                                                *
; *                                                                     *
; **********************************************************************

SSEG    SEGMENT PARA STACK 'STACK'
        DW      128 DUP(?)
SSEG    ENDS

CSEG    SEGMENT
        ASSUME  CS:CSEG,SS:SSEG
BEGIN   PROC    FAR
        PUSH    DS                      ;SET UP FOR RET TO MS-DOS
        XOR     AX,AX                   ; .
        PUSH    AX                      ; .

MAINLOOP:
        MOV     AH,6                    ;USE MS-DOS CALL TO CHECK FOR
        MOV     DL,0FFH                 ; KEYBOARD ACTIVITY
        INT     21                      ; IF NO CHARACTER, JUMP TO
        JZ      TESTCOMM                ; COMM ACTIVITY TEST

        CMP     AL,03                   ;WAS CHARACTER A Ctrl-C?
        JNE     SENDCOMM                ; NO, SEND IT TO SERIAL PORT
        RET                             ; YES, RETURN TO MS-DOS

SENDCOMM:
        MOV     AH,01                   ;USE INT 14H WRITE FUNCTION TO
        MOV     DX,0                    ; SEND DATA TO SERIAL PORT
        INT     14H                     ; .

TESTCOMM:
        MOV     AH,3                    ;GET SERIAL PORT STATUS
        MOV     DX,0                    ; .
        INT     14H                     ; .
        AND     AH,1                    ;ANY DATA WAITING?
        JZ      MAINLOOP                ; NO, GO BACK TO KEYBOARD TEST
        MOV     AH,2                    ;READ SERIAL DATA
        MOV     DX,0                    ; .
        INT     14H                     ; .
        MOV     AH,6                    ;WRITE SERIAL DATA TO SCREEN
        INT     21H                     ; .
        JMP     MAINLOOP                ;CONTINUE
BEGIN   ENDP
CSEG    ENDS
        END     BEGIN



───────────────────────────────────────────────────────────────────────────


Figure 18-10.
Correct serial test routine.


     TITLE TESTCOMM -- TEST COMMSCOP ROUTINE
; **********************************************************************
; *                                                                     *
; *  TESTCOMM                                                           *
; *     THIS ROUTINE PROVIDES DATA FOR THE COMMSCOP ROUTINE.  IT READS  *
; *  CHARACTERS FROM THE KEYBOARD AND WRITES THEM TO COM1 USING         *
; *  INT 14H.  DATA IS ALSO READ FROM INT 14H AND DISPLAYED ON THE      *
; *  SCREEN.  THE ROUTINE RETURNS TO MS-DOS WHEN Ctrl-C IS PRESSED      *
; *  ON THE KEYBOARD.                                                   *
; *                                                                     *
; **********************************************************************

SSEG    SEGMENT PARA STACK 'STACK'
        DW      128 DUP(?)
SSEG    ENDS

CSEG    SEGMENT
        ASSUME  CS:CSEG,SS:SSEG
BEGIN   PROC    FAR
        PUSH    DS                      ;SET UP FOR RET TO MS-DOS
        XOR     AX,AX                   ; .
        PUSH    AX                      ; .

MAINLOOP:
        MOV     AH,6                    ;USE DOS CALL TO CHECK FOR
        MOV     DL,0FFH                 ; KEYBOARD ACTIVITY
        INT     21H                     ; IF NO CHARACTER, JUMP TO
        JZ      TESTCOMM                ; COMM ACTIVITY TEST

        CMP     AL,03                   ;WAS CHARACTER A Ctrl-C?
        JNE     SENDCOMM                ; NO, SEND IT TO SERIAL PORT
        RET                             ; YES, RETURN TO MS-DOS

SENDCOMM:
        MOV     AH,01                   ;USE INT 14H WRITE FUNCTION TO
        MOV     DX,0                    ; SEND DATA TO SERIAL PORT
        INT     14H                     ; .

TESTCOMM:
        MOV     AH,3                    ;GET SERIAL PORT STATUS
        MOV     DX,0                    ; .
        INT     14H                     ; .
        AND     AH,1                    ;ANY DATA WAITING?
        JZ      MAINLOOP                ; NO, GO BACK TO KEYBOARD TEST
        MOV     AH,2                    ;READ SERIAL DATA
        MOV     DX,0                    ; .
        INT     14H                     ; .
        MOV     AH,6                    ;WRITE SERIAL DATA TO SCREEN
        MOV     DL,AL                   ; .
        INT     21H                     ; .
        JMP     MAINLOOP                ;CONTINUE

BEGIN   ENDP
CSEG    ENDS
        END     BEGIN



───────────────────────────────────────────────────────────────────────────


Figure 18-11.
An incorrect version of the serial trace utility.


        TITLE   BADSCOP -- BAD VERSION OF COMMUNICATIONS TRACE UTILITY
; ***********************************************************************
; *                                                                     *
; *  BADSCOP _                                                          *
; *     THIS PROGRAM MONITORS THE ACTIVITY ON A SPECIFIED COMM PORT     *
; *     AND PLACES A COPY OF ALL COMM ACTIVITY IN A RAM BUFFER.  EACH   *
; *     ENTRY IN THE BUFFER IS TAGGED TO INDICATE WHETHER THE BYTE      *
; *     WAS SENT BY OR RECEIVED BY THE SYSTEM.                          *
; *                                                                     *
; *     BADSCOP IS INSTALLED BY ENTERING                                *
; *                                                                     *
; *                     BADSCOP                                         *
; *                                                                     *

; *  THIS WILL INSTALL BADSCOP AND SET UP A 64K BUFFER TO BE USED       *
; *  FOR DATA LOGGING.  REMEMBER THAT 2 BYTES ARE REQUIRED FOR          *
; *  EACH COMM BYTE, SO THE BUFFER IS ONLY 32K EVENTS LONG, OR ABOUT    *
; *  30 SECONDS OF CONTINUOUS 9600 BAUD DATA.  IN THE REAL WORLD,       *
; *  ASYNC DATA IS RARELY CONTINUOUS, SO THE BUFFER WILL PROBABLY       *
; *  HOLD MORE THAN 30 SECONDS WORTH OF DATA.                           *
; *                                                                     *
; *  WHEN INSTALLED, BADSCOP INTERCEPTS ALL INT 14H CALLS.  IF THE      *
; *  PROGRAM HAS BEEN ACTIVATED AND THE INT IS EITHER SEND OR RE-       *
; *  CEIVE DATA, A COPY OF THE DATA BYTE, PROPERLY TAGGED, IS PLACED    *
; *  IN THE BUFFER.  IN ANY CASE, DATA IS PASSED ON TO THE REAL         *
; *  INT 14H HANDLER.                                                   *
; *                                                                     *
; *  BADSCOP IS INVOKED BY ISSUING AN INT 60H CALL.  THE INT HAS        *
; *  THE FOLLOWING CALLING SEQUENCE:                                    *
; *                                                                     *
; *       AH _ COMMAND                                                  *
; *            0 _ STOP TRACING, PLACE STOP MARK IN BUFFER              *
; *            1 _ FLUSH BUFFER AND START TRACE                         *
; *            2 _ RESUME TRACE                                         *
; *            3 _ RETURN COMM BUFFER ADDRESSES                         *
; *       DX _ COMM PORT (ONLY USED WITH AH = 1 or 2)                   *
; *            0 _ COM1                                                 *
; *            1 _ COM2                                                 *
; *                                                                     *
; *  THE FOLLOWING DATA IS RETURNED IN RESPONSE TO AH = 3:              *
; *                                                                     *
; *       CX _ BUFFER COUNT IN BYTES                                    *
; *       DX _ SEGMENT ADDRESS OF THE START OF THE BUFFER               *
; *       BX _ OFFSET ADDRESS OF THE START OF THE BUFFER                *
; *                                                                     *
; *  THE COMM BUFFER IS FILLED WITH 2-BYTE DATA ENTRIES OF THE          *
; *  FOLLOWING FORM:                                                    *
; *                                                                     *
; *       BYTE 0 _ CONTROL                                              *
; *            BIT 0 _ ON FOR RECEIVED DATA, OFF FOR TRANS.             *
; *            BIT 7 _ STOP MARK _ INDICATES COLLECTION WAS             *
; *                  INTERRUPTED AND RESUMED.                           *
; *       BYTE 1 _ 8-BIT DATA                                           *
; *                                                                     *
; **********************************************************************

        PUBLIC  INITIALIZE,CONTROL,VECTOR_INIT,COMMSCOPE
        PUBLIC  OLD_COMM_INT,COUNT,STATUS,PORT,BUFPNTR

CSEG    SEGMENT
        ASSUME  CS:CSEG,DS:CSEG
        ORG     100H                    ;TO MAKE A COM FILE

INITIALIZE:
        JMP     VECTOR_INIT             ;JUMP TO THE INITIALIZATION
                                        ; ROUTINE WHICH, TO SAVE SPACE,
                                        ; IS IN THE COMM BUFFER

;
;  SYSTEM VARIABLES
;
OLD_COMM_INT    DD      ?               ;ADDRESS OF REAL COMM INT
COUNT           DW      0               ;BUFFER COUNT
COMMSCOPE_INT   EQU     60H             ;COMMSCOPE CONTROL INT
STATUS          DB      0               ;PROCESSING STATUS
                                        ; 0 _ OFF
                                        ; 1 _ ON
PORT            DB      0               ;COMM PORT BEING TRACED
BUFPNTR         DW      VECTOR_INIT     ;NEXT BUFFER LOCATION

        SUBTTL  DATA INTERRUPT HANDLER
PAGE
; **********************************************************************
; *                                                                     *
; *  COMMSCOPE                                                          *
; *  THIS PROCEDURE INTERCEPTS ALL INT 14H CALLS AND LOGS THE DATA      *
; *  IF APPROPRIATE.                                                    *
; *                                                                     *
; **********************************************************************
COMMSCOPE       PROC    NEAR

        TEST    CS:STATUS,1             ;ARE WE ON?
        JZ      OLD_JUMP                ; NO, SIMPLY JUMP TO OLD HANDLER

        CMP     AH,00H                  ;SKIP SETUP CALLS
        JE      OLD_JUMP                ; .

        CMP     AH,03H                  ;SKIP STATUS REQUESTS
        JAE     OLD_JUMP                ; .

        CMP     AH,02H                  ;IS THIS A READ REQUEST?
        JE      GET_READ                ; YES, GO PROCESS

;
;  DATA WRITE REQUEST _ SAVE IF APPROPRIATE
;
        CMP     DL,CS:PORT              ;IS WRITE FOR PORT BEING TRACED?
        JNE     OLD_JUMP                ; NO, JUST PASS IT THROUGH

        PUSH    DS                      ;SAVE CALLER'S REGISTERS
        PUSH    BX                      ; .
        PUSH    CS                      ;SET UP DS FOR OUR PROGRAM
        POP     DS                      ; .
        MOV     BX,BUFPNTR              ;GET ADDRESS OF NEXT BUFFER
                                        ; LOCATION
        MOV     [BX],BYTE PTR 0         ;MARK AS TRANSMITTED BYTE
        MOV     [BX+1],AL               ;SAVE DATA IN BUFFER
        INC     COUNT                   ;INCREMENT BUFFER BYTE COUNT
        INC     COUNT                   ; .
        INC     BX                      ;POINT TO NEXT LOCATION
        INC     BX                      ; .
        MOV     BUFPNTR,BX              ;SAVE NEW POINTER
        JNZ     WRITE_DONE              ;ZERO INDICATES BUFFER HAS WRAPPED

        MOV     STATUS,0                ;TURN COLLECTION OFF _ BUFFER FULL
WRITE_DONE:
        POP     BX                      ;RESTORE CALLER'S REGISTERS
        POP     DS                      ; .
        JMP     OLD_JUMP                ;PASS REQUEST ON TO BIOS ROUTINE
;
;  PROCESS A READ DATA REQUEST AND WRITE TO BUFFER IF APPROPRIATE
;
GET_READ:
        CMP     DL,CS:PORT              ;IS READ FOR PORT BEING TRACED?
        JNE     OLD_JUMP                ; NO, JUST PASS IT THROUGH

        PUSH    DS                      ;SAVE CALLER'S REGISTERS
        PUSH    BX                      ; .
        PUSH    CS                      ;SET UP DS FOR OUR PROGRAM
        POP     DS                      ; .

        PUSHF                           ;FAKE INT 14H CALL
        CLI                             ; .
        CALL    OLD_COMM_INT            ;PASS REQUEST ON TO BIOS
        TEST    AH,80H                  ;VALID READ?
        JNZ     READ_DONE               ; NO, SKIP BUFFER UPDATE

        MOV     BX,BUFPNTR              ;GET ADDRESS OF NEXT BUFFER
                                        ; LOCATION
        MOV     [BX],BYTE PTR 1         ;MARK AS RECEIVED BYTE
        MOV     [BX+1],AL               ;SAVE DATA IN BUFFER
        INC     COUNT                   ;INCREMENT BUFFER BYTE COUNT
        INC     COUNT                   ; .
        INC     BX                      ;POINT TO NEXT LOCATION
        INC     BX                      ; .
        MOV     BUFPNTR,BX              ;SAVE NEW POINTER
        JNZ     READ_DONE               ;ZERO INDICATES BUFFER HAS WRAPPED

        MOV     STATUS,0                ;TURN COLLECTION OFF _ BUFFER FULL
READ_DONE:
        POP     BX                      ;RESTORE CALLER'S REGISTERS
        POP     DS                      ; .
        IRET

;
;  JUMP TO COMM BIOS ROUTINE
;
OLD_JUMP:
        JMP     OLD_COMM_INT

COMMSCOPE ENDP
        SUBTTL  CONTROL INTERRUPT HANDLER
PAGE
; **********************************************************************
; *                                                                     *
; *  CONTROL                                                            *
; *     THIS ROUTINE PROCESSES CONTROL REQUESTS.                        *
; *                                                                     *
; **********************************************************************

CONTROL PROC    NEAR
        CMP     AH,00H                  ;STOP REQUEST?
        JNE     CNTL_START              ; NO, CHECK START
        PUSH    DS                      ;SAVE REGISTERS
        PUSH    BX                      ; .
        PUSH    CS                      ;SET DS FOR OUR ROUTINE
        POP     DS
        MOV     STATUS,0                ;TURN PROCESSING OFF
        MOV     BX,BUFPNTR              ;PLACE STOP MARK IN BUFFER
        MOV     [BX],BYTE PTR 80H       ; .
        MOV     [BX+1],BYTE PTR 0FFH    ; .
        INC     COUNT                   ;INCREMENT COUNT
        INC     COUNT                   ; .
        POP     BX                      ;RESTORE REGISTERS
        POP     DS                      ; .
        JMP     CONTROL_DONE

CNTL_START:
        CMP     AH,01H                  ;START REQUEST?
        JNE     CNTL_RESUME             ; NO, CHECK RESUME
        MOV     CS:PORT,DL              ;SAVE PORT TO TRACE
        MOV     CS:BUFPNTR,OFFSET VECTOR_INIT   ;RESET BUFFER TO START
        MOV     CS:COUNT,0              ;ZERO COUNT
        MOV     CS:STATUS,1             ;START LOGGING
        JMP     CONTROL_DONE

CNTL_RESUME:
        CMP     AH,02H                  ;RESUME REQUEST?
        JNE     CNTL_STATUS             ; NO, CHECK STATUS
        CMP     CS:BUFPNTR,0            ;END OF BUFFER CONDITION?
        JE      CONTROL_DONE            ; YES, DO NOTHING
        MOV     CS:PORT,DL              ;SAVE PORT TO TRACE
        MOV     CS:STATUS,1             ;START LOGGING
        JMP     CONTROL_DONE

CNTL_STATUS:
        CMP     AH,03H                  ;RETURN STATUS REQUEST?
        JNE     CONTROL_DONE            ; NO, ERROR _ DO NOTHING
        MOV     CX,CS:COUNT             ;RETURN COUNT
        PUSH    CS                      ;RETURN SEGMENT ADDR OF BUFFER
        POP     DX                      ; .
        MOV     BX,OFFSET VECTOR_INIT   ;RETURN OFFSET ADDR OF BUFFER

CONTROL_DONE:
        IRET

CONTROL ENDP


        SUBTTL  INITIALIZE INTERRUPT VECTORS
PAGE
; **********************************************************************
; *                                                                     *
; *  VECTOR_INIT                                                        *
; *  THIS PROCEDURE INITIALIZES THE INTERRUPT VECTORS AND THEN          *
; *  EXITS VIA THE MS-DOS TERMINATE-AND-STAY-RESIDENT FUNCTION.         *
; *  A BUFFER OF 64K IS RETAINED.  THE FIRST AVAILABLE BYTE             *
; *  IN THE BUFFER IS THE OFFSET OF VECTOR_INIT.                        *
; *                                                                     *
; **********************************************************************

        EVEN                            ;ASSURE BUFFER ON EVEN BOUNDARY
VECTOR_INIT     PROC    NEAR
;
;  GET ADDRESS OF COMM VECTOR (INT 14H)
;
        MOV     AH,35H
        MOV     AL,14H
        INT     21H
;
;  SAVE OLD COMM INT ADDRESS
;
        MOV     WORD PTR OLD_COMM_INT,BX
        MOV     AX,ES
        MOV     WORD PTR OLD_COMM_INT[2],AX
;
;  SET UP COMM INT TO POINT TO OUR ROUTINE
;
        MOV     DX,OFFSET COMMSCOPE
        MOV     AH,25H
        MOV     AL,14H
        INT     21H
;
;  INSTALL CONTROL ROUTINE INT
;
        MOV     DX,OFFSET CONTROL
        MOV     AH,25H
        MOV     AL,COMMSCOPE_INT
        INT     21H
;
;  SET LENGTH TO 64K, EXIT AND STAY RESIDENT
;
        MOV     AX,3100H                ;TERM AND STAY RES COMMAND
        MOV     DX,1000H                ;64K RESERVED
        INT     21H                     ;DONE
VECTOR_INIT ENDP

CSEG    ENDS
        END     INITIALIZE



───────────────────────────────────────────────────────────────────────────


Figure 18-12.
COMMSCMD.COD.


;       Static Name Aliases
;
;       $S142_commands  EQU     commands
        TITLE   commscmd
;       NAME    commscmd.C

        .287
_TEXT   SEGMENT  BYTE PUBLIC 'CODE'
_TEXT   ENDS
_DATA   SEGMENT  WORD PUBLIC 'DATA'
_DATA   ENDS
CONST   SEGMENT  WORD PUBLIC 'CONST'
CONST   ENDS
_BSS    SEGMENT  WORD PUBLIC 'BSS'
_BSS    ENDS
DGROUP  GROUP   CONST,  _BSS,   _DATA
        ASSUME  CS: _TEXT, DS: DGROUP, SS: DGROUP, ES: DGROUP
EXTRN   _int86:NEAR
EXTRN   _printf:NEAR
EXTRN   _stricmp:NEAR
EXTRN   _atoi:NEAR
EXTRN   __chkstk:NEAR
_DATA      SEGMENT

$SG148  DB    'STOP',  00h
$SG151  DB    'START',  00h
$SG154  DB    'RESUME',  00h
$SG157  DB    0ah, 'Communications tracing %s for port COM%1d:',  0ah,  00h
$S142_commands  DB      'STOPPED',  00h
        ORG   $+2
        DB    'STARTED',  00h
        ORG   $+2
        DB    'RESUMED',  00h
        ORG   $+2
_DATA      ENDS
_TEXT      SEGMENT
;***  /*******************************************************************
;|***  *                                                                  *
;|***  *  COMMSCMD                                                        *
;|***  *                                                                  *
;|***  *  This routine controls the COMMSCOP program that has been in-    *
;|***  *  stalled as a resident routine.  The operation performed is de-  *
;|***  *  termined by the command line.  The COMMSCMD program is invoked  *
;|***  *  as follows:                                                     *
;|***  *                                                                  *
;|***  *                COMMSCMD [[cmd][ port]]                           *
;|***  *                                                                  *
;|***  *  where cmd is the command to be executed                         *
;|***  *            STOP   -- stop trace                                  *
;|***  *            START  -- flush trace buffer and start trace          *
;|***  *            RESUME -- resume a stopped trace                      *
;|***  *        port is the COMM port to be traced (1=COM1, 2=COM2, etc.) *
;|***  *                                                                  *
;|***  *  If cmd is omitted, STOP is assumed.  If port is omitted, 1 is   *
;|***  *  assumed.                                                        *
;|***  *                                                                  *
;|***  *******************************************************************/
;|***
;|*** #include <stdlib.h>
;|*** #include <stdio.h>
;|*** #include <dos.h>
;|*** #define COMMSCMD 0x60
;|***
;|*** main(argc, argv)
;|*** int argc;
; Line 29
        PUBLIC _main
_main   PROC NEAR
        *** 000000     55                      push   bp
        *** 000001     8b ec                   mov    bp,sp
        *** 000003     b8 22 00                mov    ax,34
        *** 000006     e8 00 00                call   __chkstk
        *** 000009     57                      push   di
        *** 00000a     56                      push   si
;|*** char *argv[];
;|*** {
; Line 31
;       argc = 4
;       argv = 6
;       cmd = -4
;       port = -6
;       result = -2
;       inregs = -34
;       outregs = -20
;|***     int cmd, port, result;
;|***     static char commands[3] [10] = {"STOPPED", "STARTED", "RESUMED"};
;|***     union REGS inregs, outregs;
;|***
;|***     cmd = 0;
; Line 36
        *** 00000b     c7 46 fc 00 00          mov     WORD PTR [bp-4],0    ;
;|***     port = 0;
; Line 37
        *** 000010     c7 46 fa 00 00          mov     WORD PTR [bp-6],0    ;
;|***
;|***     if (argc > 1)
; Line 39
        *** 000015     83 7e 04 01            cmp     WORD PTR [bp+4],1    ;a
        *** 000019     7f 03                  jg      $JCC25
        *** 00001b     e9 5d 00               jmp     $I145
                                     $JCC25:
;|***      {
; Line 40
;|***      if (0 == stricmp(argv[1], "STOP"))
; Line 41
        *** 00001e     b8 00 00               mov     ax,OFFSET DGROUP:$SG148
        *** 000021     50                     push    ax
        *** 000022     8b 5e 06               mov     bx,[bp+6]            ;a
        *** 000025     ff 77 02               push    WORD PTR [bx+2]
        *** 000028     e8 00 00               call    _stricmp
        *** 00002b     83 c4 04               add     sp,4
        *** 00002e     3d 00 00               cmp     ax,0
        *** 000031     74 03                  je      $JCC49
        *** 000033     e9 08 00               jmp     $I147
                                     $JCC49:
;|***             cmd = 0;
; Line 42
        *** 000036      c7 46 fc 00 00         mov     WORD PTR [bp-4],0   ;c
;***         else if (0 == stricmp(argv[1], "START"))
; Line 43
        *** 00003b     e9 3d 00                jmp     $I149
                                      $I147:
        *** 00003e     b8 05 00                mov     ax,OFFSET DGROUP:$SG15
        *** 000041     50                      push    ax
        *** 000042     8b 5e 06                mov     bx,[bp+6]     ;argv
        *** 000045     ff 77 02                push    WORD PTR [bx+2]
        *** 000048     e8 00 00                call    _stricmp
        *** 00004b     83 c4 04                add     sp,4
        *** 00004e     3d 00 00                mp      ax,0
        *** 000051     74 03                   je      $JCC81
        *** 000053     e9 08 00                jmp     $I150
                                     $JCC81:
;|***            cmd = 1;
; Line 44
        *** 000056     c7 46 fc 01 00          mov     WORD PTR [bp-4],1
;|***         else if (0 == stricmp(argv[1], "RESUME"))
; Line 45
        *** 00005b     e9 1d 00                jmp     $I152
                                      $I150:
        *** 00005e     b8 0b 00                mov     ax,OFFSET DGROUP:$SG15
        *** 000061     50                      push    ax
        *** 000062     8b 5e 06                mov     bx,[bp+6]     ;argv
        *** 000065     ff 77 02                push    WORD PTR [bx+2]
        *** 000068     e8 00 00                call    _stricmp
        *** 00006b     83 c4 04                add     sp,4
        *** 00006e     3d 00 00                cmp     ax,0
        *** 000071     74 03                   je      $JCC113
        *** 000073     e9 05 00                mp     $I153
                                    $JCC113:
;|***            cmd = 2;
; Line 46
        *** 000076     c7 46 fc 02 00          mov     WORD PTR [bp-4],2
;|***         }
; Line 47
                                      $I153:
                                      $I152:
                                      $I149:
;|***
;|***     if (argc == 3)
; Line 49
                                      $I145:
        *** 00007b     83 7e 04 03             cmp     WORD PTR [bp+4],3
        *** 00007f     74 03                   je      $JCC127
        *** 000081     e9 1b 00                jmp     $I155
                                    $JCC127:
;|***         {
; Line 50
;|***         port = atoi(argv[2]);
; Line 51
        *** 000084     8b 5e 06                mov     bx,[bp+6]
        *** 000087     ff 77 04                push    WORD PTR [bx+4]
        *** 00008a     e8 00 00                call    _atoi
        *** 00008d     83 c4 02                add     sp,2
        *** 000090     89 46 fa                mov     [bp-6],ax
;|***         if (port > 0)
; Line 52
        *** 000093     83 7e fa 00             cmp     WORD PTR [bp-6],0
        *** 000097     7f 03                   jg      $JCC151
        *** 000099     e9 03 00                jmp     $I156
                                    $JCC151:
;|***              port = port-1;
; Line 53
        *** 00009c     ff 4e fa                dec     WORD PTR [bp-6]
;|***         }
; Line 54
                                      $I156:
;|***
;|***     inregs.h.ah = cmd;
; Line 56
                                      $I155:
        *** 00009f     8a 46 fc                mov     al,[bp-4]
        *** 0000a2     88 46 df                mov     [bp-33],al
;|***     inregs.x.dx = port;
; Line 57
        *** 0000a5     8b 46 fa                mov     ax,[bp-6]
        *** 0000a8     89 46 e4                mov     [bp-28],ax
;|***     result = int86(COMMCMD, &inregs, &outregs);
; Line 58
        *** 0000ab     8d 46 ec                lea     ax,[bp-20]
        *** 0000ae     50                      push    ax
        *** 0000af     8d 46 de                lea     ax,[bp-34]
        *** 0000b2     50                      push    ax
        *** 0000b3     b8 60 00                mov     ax,96
        *** 0000b6     50                      push    ax
        *** 0000b7     e8 00 00                call    _int86
        *** 0000ba     83 c4 06                add     sp,6
        *** 0000bd     89 46 fe                mov     [bp-2],ax
;|***
;|***
;|***     printf("\nCommunications tracing %s for port COM%1d:\n",
;|***             commands[cmd], port + 1);
; Line 62
        *** 0000c0     8b 46 fa               mov     ax,[bp-6]             ;
        *** 0000c3     40                     inc     ax
        *** 0000c4     50                     push    ax
        *** 0000c5     8b 46 fc               mov     ax,[bp-4]             ;
        *** 0000c8     8b c8                  mov     cx,ax
        *** 0000ca     d1 e0                  shl     ax,1
        *** 0000cc     d1 e0                  shl     ax,1
        *** 0000ce     03 c1                  add     ax,cx
        *** 0000d0     d1 e0                  shl     ax,1
        *** 0000d2     05 40 00               add     ax,OFFSET DGROUP:$S142_
        *** 0000d5     50                     push    ax
        *** 0000d6     b8 12 00               mov     ax,OFFSET DGROUP:$SG157
        *** 0000d9     50                     push    ax
        *** 0000da     e8 00 00               call    _printf
        *** 0000dd     83 c4 06               add     sp,6
;|*** }
; Line 63
                                     $EX138:
        *** 0000e0     5e                     pop     si
        *** 0000e1     5f                     pop     di
        *** 0000e2     8b e5                  mov     sp,bp
        *** 0000e4     5d                     pop     bp
        *** 0000e5     c3                     ret

_main     ENDP
_TEXT     ENDS
END



───────────────────────────────────────────────────────────────────────────


Figure 18-13.
An erroneous C program to convert a string to uppercase.


/************************************************************************
 *                                                                       *
 * UPPERCAS.C                                                            *
 *    This routine converts a fixed string to uppercase and prints it.   *
 *                                                                       *
 ************************************************************************/

#include <ctype.h>
#include <string.h>
#include <stdio.h>

main(argc,argv)

int argc;
char *argv[];

{
char    *cp,c;

        cp = "a string\n";

        /*  Convert *cp to uppercase and write to standard output  */

        while (*cp != '\0')
                {
                c = toupper(*cp++);
                putchar(c);
                }

}



───────────────────────────────────────────────────────────────────────────


Figure 18-14.
The corrected version of UPPERCAS.C.


/************************************************************************
 *                                                                       *
 * UPPERCAS.C                                                            *
 *    This routine converts a fixed string to uppercase and prints it.   *
 *                                                                       *
 ************************************************************************/

#include <ctype.h>
#undef toupper
#undef tolower
#include <string.h>
#include <stdio.h>

main(argc,argv)

int argc;
char *argv[];

{
char    *cp,c;

        cp = "a string\n";

        /*  Convert *cp to uppercase and write to standard output  */

        while (*cp != '\0')
                {
                c = toupper(*cp++);
                putchar(c);
                }

}



───────────────────────────────────────────────────────────────────────────


Figure 18-16.
An erroneous program to display ASCII characters.


/************************************************************************
 *                                                                       *
 *  ASCTBL.C                                                             *
 *   This program generates an ASCII lookup table for all displayable    *
 *   ASCII and extended IBM PC codes, leaving blanks for nondisplayable  *
 *   codes.                                                              *
 *                                                                       *
 ************************************************************************/

#include <ctype.h>
#include <stdio.h>

main()
{
int i, j, k;
        /* Print table title. */
        printf("\n\n\n                ASCII LOOKUP TABLE\n\n");

        /* Print column headers. */
        printf("    ");
        for (i = 0; i < 16; i++)
                printf("%X  ", i);
        fputchar("\n");
        /* Print each line of the table. */
        for ( i = 0, k = 0; i < 16; i++)
                {
                /* Print first hex digit of symbols on this line. */
                printf("%X   ", i);
                /* Print each of the 16 symbols for this line. */
                for (j = 0; j < 16; j++)
                       {
                       /* Filter nonprintable characters. */
                       if ((k >= 7 && k <= 13) $LS$LS (k >= 28 && k <= 31))
                              printf("   ");
                       else
                              printf("%c  ", k);
                       k++;
                       }
                  fputchar("\n");
                  }
}



───────────────────────────────────────────────────────────────────────────


Figure 18-17.
The correct ASCII table generation program.


/************************************************************************
 *                                                                       *
 *  ASCTBL.C                                                             *
 *   This program generates an ASCII lookup table for all displayable    *
 *   ASCII and extended IBM PC codes, leaving blanks for nondisplayable  *
 *   codes.                                                              *
 *                                                                       *
 ************************************************************************/

#define LINT_ARGS
#include <ctype.h>
#include <stdio.h>

main()
{
int i, j, k;
        /* Print table title. */
        printf("\n\n\n                ASCII LOOKUP TABLE\n\n");

        /* Print column headers. */
        printf("    ");
        for (i = 0; i < 16; i++)
                printf("%X  ", i);
        fputchar('\n');

        /* Print each line of the table. */
        for (i = 0, k = 0; i < 16; i++)
               {
               /* Print first hex digit of symbols on this line. */
               printf("%X   ", i);
               /* Print each of the 16 symbols for this line. */
               for (j = 0; j < 16; j++)
                       {
                       /* Filter nonprintable characters. */
                       if ((k >= 7 && k <= 13) || (k >= 28 && k <= 31))
                              printf("   ");
                       else
                              printf("%c  ", k);
                       k++;
                       }
               fputchar('\n');
               }
}



───────────────────────────────────────────────────────────────────────────


Figure 19-2.
A sample "program" containing statements from which the assembler derives
fixup information.


                                       title   fixups


                               FarGroup GROUP  FarSeg1,FarSeg2

0000                           CodeSeg SEGMENT byte public 'CODE'
                                       ASSUME  cs:CodeSeg

0000  9A 0000 ---- R           start:  call    far ptr FarProc

0005                           CodeSeg ENDS


0000                           FarSeg1 SEGMENT byte public     ;part of
                                                               ;FarGroup

0000                           FarSeg1 ENDS


0000                           FarSeg2 SEGMENT byte public
                                       ASSUME  cs:FarGroup

0000                           FarProc PROC    far
0000  CB                               ret                     ;a FAR
                                                               ;return
                               FarProc ENDP

0001                           FarSeg2 ENDS

                                       END



───────────────────────────────────────────────────────────────────────────


Figure 19-3.
The source code for HELLO.ASM.


        NAME    HELLO

_TEXT   SEGMENT byte public 'CODE'

        ASSUME  cs:_TEXT,ds:_DATA

start:                                  ;program entry point
        mov     ax,seg msg
        mov     ds,ax
        mov     dx,offset msg           ;DS:DX -> msg
        mov     ah,09h
        int     21h                     ;perform int 21H function 09H
                                        ;(Output character string)
        mov     ax,4C00h
        int     21h                     ;perform int 21H function 4CH
                                        ;(Terminate with return code)
_TEXT   ENDS


_DATA   SEGMENT word public 'DATA'

msg     DB      'Hello, world',0Dh,0Ah,'$'

_DATA   ENDS


_STACK  SEGMENT stack 'STACK'

        DW      80h dup(?)              ;stack depth = 128 words

_STACK  ENDS

        END     start



───────────────────────────────────────────────────────────────────────────


Figure 19-6.
A sample "program" showing how some common fixups are encoded in FIXUPP
records.


                                   TITLE   fixupps
                           _TEXT   SEGMENT byte public 'CODE'
                                   ASSUME  cs:_TEXT
                                   EXTRN   NearLabel:near
                                   EXTRN   FarLabel:far

0000                       NearProc        PROC    near

0000  E9 0000 E                    jmp     NearLabel       ;relocatable
                                                           ;word offset
0003  EB 00 E                      jmp     short NearLabel ;relocatable
                                                           ;byte offset
0005  EA 0000 ---- R               jmp     far ptr FarProc ;far jump to a
                                                           ;known seg
000A  EA 0000 ---- E               jmp     FarLabel     ;far jump to an
                                                        ;unknown seg

000F  BB 0015 R                    mov     bx,offset LocalLabel ;relocat-
                                                                ;able
                                                                ;offset
0012  B8 ---- R                    mov     ax,seg LocalLabel    ;relocat-
                                                                ;able seg

0015  C3                   LocalLabel:     ret

                           NearProc        ENDP

0016                       _TEXT   ENDS


0000                       FAR_TEXT        SEGMENT byte public 'FAR_CODE'
                                   ASSUME  cs:FAR_TEXT

0000                       FarProc PROC    far

0000  CB                           ret

                           FarProc ENDP

0001                       FAR_TEXT        ENDS

                                   END



───────────────────────────────────────────────────────────────────────────


Figure 20-10.
Segment order for a terminate-and-stay-resident program.


ResidentCodeSeg     SEGMENT    para                (Low Memory)
                    .
                    .(executable code)
                    .
ResidentCodeSeg     ENDS

ResidentDataSeg     SEGMENT    word
                    .
                    .(program data)
                    .
ResidentDataSeg     ENDS

StackSeg            SEGMENT    para
                    .
                    .(stack)
                    .
StackSeg            ENDS                           Resident portion
----------------------------------                 -----------------------
TransientCodeSeg    SEGMENT    para                Transient portion
                    .
                    .(executable code)
                    .
TransientCodeSeg    ENDS

TransientDataSeg    SEGMENT    word
                    .
                    .(program data)
                    .
TransientDataSeg    ENDS                           (High Memory)



Figure L-6.
QuickBASIC binary-to-hexadecimal file conversion utility.


'Binary-to-Hex file conversion utility.
'Requires Microsoft QuickBASIC version 3.0 or later.

DEFINT A-Z                                    ' All variables are integers
                                              ' unless otherwise declared.
CONST FALSE = 0                               ' Value of logical FALSE.
CONST TRUE = NOT FALSE                        ' Value of logical TRUE.

DEF FNHXB$(X) = RIGHT$(HEX$(&H100 + X), 2)    ' Return 2-digit hex value
                                              ' for X.
DEF FNHXW$(X!) = RIGHT$("000" + HEX$(X!), 4)  ' Return 4-digit hex value
                                              ' for X!.
DEF FNMOD(X, Y) = X! - INT(X!/Y) * Y          ' X! MOD Y (the MOD operation
                                              ' is only for integers).
CONST SRCCNL = 1                              ' Source (.BIN) file
                                              ' channel.
CONST TGTCNL = 2                              ' Target (.HEX) file
                                              ' channel.

LINE INPUT "Enter full name of source .BIN file        :  ";SRCFIL$
OPEN SCRCFIL$ FOR INPUT AS SRCCNL             ' Test for source (.BIN)
                                              ' file.
SRCSIZ! = LOF(SRCCNL)                         ' Save file's size.
CLOSE SRCCNL
IF (SRCSIZ! > 65536) THEN                     ' Reject if file exceeds
                                              ' 64 KB.
    PRINT "Cannot convert file larger than 64 KB."
    END
END IF

LINE INPUT "Enter full name of target .HEX file        :  ";TGTFIL$
OPEN TGTFIL$ FOR OUTPUT AS TGTCNL             ' Test target (.HEX)
                                              ' filename.
CLOSE TGTCNL
DO
    LINE INPUT "Enter starting address of .BIN file in HEX :  ";L$
    ADRBGN! = VAL("&H" + L$)                  ' Convert ASCII HEX
                                              ' address value
                                              ' to binary value.
    IF (ADRBGN! < 0) THEN                     ' HEX values 8000-FFFFH
                                              ' convert
     ADRBGN! = 65536 + ADRBGN!                ' to negative values.
    END IF
    ADREND! = ADRBGN! + SRCSIZ! - 1           ' Calculate resulting
                                              ' end address.
    IF (ADREND! > 65535) THEN                 ' Reject if address
                                              ' exceeds FFFFH.
     PRINT "Entered start address causes end address to exceed FFFFH."
    END IF
LOOP UNTIL (ADRFLD! >= 0) AND (ADRFLD! <= 65535) AND (ADREND! <= 65535)

DO
    LINE INPUT "Enter byte count for each record in HEX    :  ";L$
    SRCRLN = VAL("&H" + L$)                   ' Convert ASCII HEX
                                              ' max record length
                                              ' value to binary value.
    IF (SRCRLN < 0) THEN                      ' HEX values 8000-FFFFH
                                              ' convert
     SRCRLN = 65536 + SRCRLN                  ' to negative values.
    END IF
LOOP UNTIL (SRCRLN > 0) AND (SRCRLN < 256)    ' Ask again if not 1-255.

OPEN SRCFIL$ AS SRCCNL LEN = SRCRLN           ' Reopen source for block
                                              ' I/O.
FIELD#SRCCNL,SRCRLN AS SRCBLK$
OPEN TGTFIL$ FOR OUTPUT AS TGTCNL             ' Reopen target for
                                              ' text output.
SRCREC = 0                                    ' Starting source block #
                                              ' minus 1.

FOR ADRFLD! = ADRBGN! TO ADREND! STEP SRCRLN  ' Convert one block per loop.
    SRCREC = SRCREC + 1                       ' Next source block.
    GET SRCCNL,SRCREC                         ' Read the source block.
    IF (ADRFLD! + SRCRLN > ADREND!) THEN      ' If last block less than
     BLK$=LEFT$(SRCBLK$,ADREND!-ADRFLD!+1)    ' full size:  trim it.
    ELSE                                      ' Else:
        BLK$ = SRCBLK$                        ' Use full block.
    END IF

    PRINT#TGTCNL, ":";                        ' Write record mark.

    PRINT#TGTCNL, FNHXB$(LEN(BLK$));          ' Write data field size.
    CHKSUM = LEN(BLK$)                        ' Initialize checksum
                                              ' accumulate
                                              ' with first value.
    PRINT#TGTCNL,FNHXW$(ADRFLD!);             ' Write record's load
                                              ' address.

' The following "AND &HFF" operations limit CHKSUM to a byte value.
    CHKSUM = CHKSUM + INT(ADRFLD!/256) AND &HFF   ' Add hi byte of adrs
                                                  ' to csum.
    CHKSUM = CHKSUM + FNMOD(ADRFLD!,256) AND &HFF ' Add lo byte of adrs
                                                  ' to csum.

    PRINT#TGTCNL,FNHXB$(0);                   ' Write record type.
' Don't bother to add record type byte to checksum since it's 0.
    FOR IDX = 1 TO LEN(BLK$)                  ' Write all bytes.
     PRINT#TGTCNL,FNHXB$(ASC(MID$(BLK$,IDX,1)));      ' Write next byte.
     CHKSUM = CHKSUM + ASC(MID$(BLK$,IDX,1)) AND &HFF ' Incl byte in csum.
    NEXT IDX

    CHKSUM = 0 - CHKSUM AND &HFF              ' Negate checksum then
                                              ' limit
                                              ' to byte value.
    PRINT #TGTCNL,FNHXB$(CHKSUM)              ' End record with checksum.

NEXT ADRFLD!

PRINT#TGTCNL, ":00000001FF"                   ' Write end-of-file record.

CLOSE TGTCNL                                  ' Close target file.
CLOSE SRCCNL                                  ' Close source file.

END

Return to The MS-DOS Encyclopedia: Contents