PCjs Machines

Home of the original IBM PC emulator for browsers.

Logo

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