Metropoli BBS
VIEWER: talk.asm MODE: TEXT (ASCII)
	title	TALK --- Simple terminal emulator
        page    55,132
        .lfcond             ;list false conditionals too

;
; TALK.ASM  --- Simple IBM PC terminal emulator
; 
; Copyright (c) 1988 Ray Duncan
;
; Build:  MASM TALK;
;         LINK TALK;
;
; Usage:  TALK
;

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

cr      equ     0dh             ; ASCII carriage return
lf      equ     0ah             ; ASCII line feed
bsp     equ     08h             ; ASCII backspace
escape  equ     1bh             ; ASCII escape code

dattr   equ     07h             ; display attribute to use
                                ; while in emulation mode

bufsiz  equ     4096            ; size of serial port buffer

echo    equ     0               ; 0=full-duplex, -1=half-duplex

true    equ     -1
false   equ     0

com1    equ     true            ; Use COM1 if nonzero
com2    equ     not com1        ; Use COM2 if nonzero

pic_mask  equ   21h             ; 8259 int. mask port
pic_eoi   equ   20h             ; 8259 EOI port
        
        if      com1
com_data equ    03f8h           ; port assignments for COM1 
com_ier  equ    03f9h
com_mcr  equ    03fch
com_sts  equ    03fdh
com_int  equ    0ch             ; COM1 interrupt number
int_mask equ    10h             ; IRQ4 mask for 8259
        endif

        if      com2
com_data equ    02f8h           ; port assignments for COM2
com_ier  equ    02f9h
com_mcr  equ    02fch
com_sts  equ    02fdh
com_int  equ    0bh             ; COM2 interrupt number 
int_mask equ    08h             ; IRQ3 mask for 8259
        endif

_TEXT   segment word public 'CODE'

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

talk    proc    far             ; entry point from MS-DOS

        mov     ax,_DATA        ; make data segment addressable 
        mov     ds,ax
        mov     es,ax
                                ; initialize display for 
                                ; terminal emulator mode...

        mov     ah,15           ; get display width and 
        int     10h             ; current display mode
        dec     ah              ; save display width for use
        mov     columns,ah      ; by the screen clear routine

        cmp     al,7            ; enforce text display mode
        je      talk2           ; mode 7 ok, proceed

        cmp     al,3
        jbe     talk2           ; modes 0-3 ok,proceed

        mov     dx,offset msg1
        mov     cx,msg1_len
        jmp     talk6           ; print error message and exit

talk2:  mov     bh,dattr        ; clear screen and home cursor
        call    cls

        call    asc_enb         ; capture serial port interrupt
                                ; vector and enable interrupts

        mov     dx,offset msg2  ; display message
        mov     cx,msg2_len     ; 'Terminal emulator running'
        mov     bx,stdout       ; BX = standard output handle
        mov     ah,40h          ; Fxn 40H = write file or device
        int     21h             ; transfer to MS-DOS

talk3:  call    pc_stat         ; keyboard character waiting?
        jz      talk4           ; nothing waiting, jump

        call    pc_in           ; read keyboard character

        cmp     al,0            ; is it a function key?
        jne     talk32          ; not function key, jump

        call    pc_in           ; function key, discard 2nd
                                ; character of sequence
        jmp     talk5           ; then terminate program

talk32:                         ; keyboard character received
        if      echo
        push    ax              ; if half-duplex, echo
        call    pc_out          ; character to PC display
        pop     ax              
        endif

        call    com_out         ; write char. to serial port

talk4:  call    com_stat        ; serial port character waiting?
        jz      talk3           ; nothing waiting, jump

        call    com_in          ; read serial port character

        cmp     al,20h          ; is it control code?
        jae     talk45          ; jump if not

        call    ctrl_code       ; control code, process it

        jmp     talk3           ; check keyboard again

talk45:                         ; non-control char. received,
        call    pc_out          ; write it to PC display

        jmp     talk4           ; see if any more waiting

talk5:                          ; function key detected, 
                                ; prepare to terminate...

        mov     bh,07h          ; clear screen and home cursor
        call    cls

        mov     dx,offset msg3  ; display farewell message
        mov     cx,msg3_len

talk6:  push    dx              ; save message address
        push    cx              ; and message length

        call    asc_dsb         ; disable serial port interrupts
                                ; and release interrupt vector

        pop     cx              ; restore message length
        pop     dx              ; and address

        mov     bx,stdout       ; handle for standard output
        mov     ah,40h          ; Fxn 40H = write device
        int     21h             ; transfer to MS-DOS    

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

talk    endp


com_stat proc   near            ; Check asynch status, returns  
                                ; Z=false if character ready
                                ; Z=true if nothing waiting
        push    ax
        mov     ax,asc_in       ; compare ring buffer pointers
        cmp     ax,asc_out
        pop     ax
        ret                     ; return to caller      

com_stat endp


com_in  proc    near            ; get character from serial
                                ; port buffer, returns
                                ; new character in AL

        push    bx              ; save register BX

com_in1:                        ; if no char waiting, wait
        mov     bx,asc_out      ; until one is received
        cmp     bx,asc_in
        je      com_in1         ; jump, nothing waiting

        mov     al,[bx+asc_buf] ; character is ready,
                                ; extract it from buffer

        inc     bx              ; update buffer pointer 
        cmp     bx,bufsiz
        jne     com_in2
        xor     bx,bx           ; reset pointer if wrapped
com_in2:                        
        mov     asc_out,bx      ; store updated pointer
        pop     bx              ; restore register BX
        ret                     ; and return to caller

com_in  endp


com_out proc    near            ; write character in AL 
                                ; to serial port   

        push    dx              ; save register DX
        push    ax              ; save character to send
        mov     dx,com_sts      ; DX = status port address

com_out1:                       ; check if transmit buffer
        in      al,dx           ; is empty (TBE bit = set)
        and     al,20h
        jz      com_out1        ; no, must wait

        pop     ax              ; get character to send
        mov     dx,com_data     ; DX = data port address
        out     dx,al           ; transmit the character
        pop     dx              ; restore register DX
        ret                     ; and return to caller

com_out endp


pc_stat proc    near            ; read keyboard status, returns
                                ; Z=false if character ready
                                ; Z=true if nothing waiting.
                                ; register DX destroyed.

        mov     al,in_flag      ; if character already    
        or      al,al           ; waiting, return status   
        jnz     pc_stat1

        mov     ah,6            ; otherwise call MS-DOS to
        mov     dl,0ffh         ; determine keyboard status
        int     21h

        jz      pc_stat1        ; jump if no key ready

        mov     in_char,al      ; got key, save it for
        mov     in_flag,0ffh    ; "pc_in" routine

pc_stat1:                       ; return to caller with
        ret                     ; Z flag set appropriately

pc_stat endp


pc_in   proc    near            ; read keyboard character,
                                ; return it in AL
                                ; DX may be destroyed.

        mov     al,in_flag      ; key already waiting?  
        or      al,al
        jnz     pc_in1          ; yes,return it to caller

        call    pc_stat         ; try and read a character
        jmp     pc_in

pc_in1: mov     in_flag,0       ; clear char. waiting flag
        mov     al,in_char      ; and return AL = character
        ret 

pc_in   endp


pc_out  proc    near            ; write character in AL
                                ; to the PC's display.

        mov     ah,0eh          ; ROM BIOS Fxn 0EH = 
                                ; "teletype output"
        push    bx              ; save register BX
        xor     bx,bx           ; assume page 0
        int     10h             ; transfer to ROM BIOS
        pop     bx              ; restore register BX
        ret                     ; and return to caller

pc_out  endp


cls     proc    near            ; clear display using 
                                ; char. attribute in BH
                                ; registers AX, CX, 
                                ; and DX destroyed.

        mov     dl,columns      ; set DL,DH = X,Y of
        mov     dh,24           ; lower right corner
        mov     cx,0            ; set CL,CH=X,Y of
                                ; upper left corner
        mov     ax,600h         ; ROM BIOS Fxn 06H =
                                ; "scroll or initialize
                                ; window" 
        int     10h             ; transfer to ROM BIOS
        call    home            ; set cursor at (0,0)
        ret                     ; and return to caller

cls     endp


clreol  proc    near            ; clear from cursor to end
                                ; of line using attribute
                                ; in BH.  Register AX, CX,
                                ; and DX destroyed.

        call    getxy           ; get current cursor pos'n
        mov     cx,dx           ; current position = "upper 
                                ; left corner" of window
        mov     dl,columns      ; "lower right corner" X is 
                                ; max columns, Y is same
                                ; as upper left corner
        mov     ax,600h         ; ROM BIOS Fxn 06H = 
                                ; "scroll or initialize
                                ; window"
        int     10h             ; transfer to ROM BIOS
        ret                     ; return to caller

clreol  endp


home    proc    near            ; put cursor at home position

        mov     dx,0            ; set (X,Y) = (0,0)
        call    gotoxy          ; position the cursor
        ret                     ; return to caller

home    endp


gotoxy  proc    near            ; position the cursor
                                ; call with DL=X, DH=Y

        push    bx              ; save registers
        push    ax

        mov     bh,0            ; assume page 0
        mov     ah,2            ; ROM BIOS Fxn 02H = 
                                ; set cursor position
        int     10h             ; transfer to ROM BIOS

        pop     ax              ; restore registers
        pop     bx
        ret                     ; and return to caller

gotoxy  endp


getxy   proc    near            ; get cursor position,
                                ; Returns DL=X, DH=Y

        push    ax              ; save registers
        push    bx
        push    cx

        mov     ah,3            ; ROM BIOS Fxn 03H =
                                ; get cursor position
        mov     bh,0            ; assume page 0
        int     10h             ; transfer to ROM BIOS

        pop     cx              ; restore registers
        pop     bx
        pop     ax
        ret                     ; and return to caller

getxy   endp


ctrl_code proc  near            ; process control code
                                ; call with AL=char

        cmp     al,cr           ; if carriage return
        je      ctrl8           ; just send it  

        cmp     al,lf           ; if line feed
        je      ctrl8           ; just send it

        cmp     al,bsp          ; if backspace
        je      ctrl8           ; just send it

        cmp     al,26           ; is it cls control code?
        jne     ctrl7           ; no, jump

        mov     bh,dattr        ; cls control code, clear
        call    cls             ; screen and home cursor

        jmp     ctrl9

ctrl7:
        cmp     al,escape       ; is it Escape character?
        jne     ctrl9           ; no, throw it away

        call    esc_seq         ; yes, emulate CRT terminal
        jmp     ctrl9

ctrl8:  call    pc_out          ; send CR, LF, or backspace
                                ; to the display

ctrl9:  ret                     ; return to caller

ctrl_code endp


esc_seq proc    near            ; decode Televideo 950 escape
                                ; sequence for screen control

        call    com_in          ; get next character
        cmp     al,84           ; is it clear to end of line?
        jne     esc_seq1        ; no, jump

        mov     bh,dattr        ; yes, clear to end of line
        call    clreol
        jmp     esc_seq2        ; then exit     

esc_seq1:
        cmp     al,61           ; is it cursor positioning?
        jne     esc_seq2        ; no jump

        call    com_in          ; yes, get Y parameter
        sub     al,33           ; and remove offset
        mov     dh,al

        call    com_in          ; get X parameter
        sub     al,33           ; and remove offset
        mov     dl,al
        call    gotoxy          ; position the cursor 

esc_seq2:                       ; return to caller
        ret

esc_seq endp


asc_enb proc    near            ; capture serial port interrupt
                                ; vector and enable interrupt

                                ; save address of previous
                                ; interrupt handler...
        mov     ax,3500h+com_int ; Fxn 35H = get vector
        int     21h             ; transfer to MS-DOS
        mov     word ptr oldvec+2,es
        mov     word ptr oldvec,bx

                                ; now install our handler...
        push    ds              ; save our data segment
        mov     ax,cs           ; set DS:DX = address
        mov     ds,ax           ; of our interrupt handler
        mov     dx,offset asc_int
        mov     ax,2500h+com_int ; Fxn 25H = set vector
        int     21h             ; transfer to MS-DOS
        pop     ds              ; restore data segment
        
        mov     dx,com_mcr      ; set modem control register
        mov     al,0bh          ; DTR and OUT2 bits
        out     dx,al

        mov     dx,com_ier      ; set interrupt enable 
        mov     al,1            ; register on serial
        out     dx,al           ; port controller

        in      al,pic_mask     ; read current 8259 mask
        and     al,not int_mask ; set mask for COM port
        out     pic_mask,al     ; write new 8259 mask

        ret                     ; back to caller

asc_enb endp
         

asc_dsb proc    near            ; disable interrupt and 
                                ; release interrupt vector
        
        in      al,pic_mask     ; read current 8259 mask
        or      al,int_mask     ; reset mask for COM port
        out     pic_mask,al     ; write new 8259 mask

        push    ds              ; save our data segment
        lds     dx,oldvec       ; load address of
                                ; previous interrupt handler    
        mov     ax,2500h+com_int ; Fxn 25H = set vector
        int     21h             ; transfer to MS-DOS    
        pop     ds              ; restore data segment

        ret                     ; back to caller

asc_dsb endp


asc_int proc    far             ; interrupt service routine
                                ; for serial port

        sti                     ; turn interrupts back on

        push    ax              ; save registers
        push    bx
        push    dx
        push    ds

        mov     ax,_DATA        ; make our data segment 
        mov     ds,ax           ; addressable

        cli                     ; clear interrupts for
                                ; pointer manipulation

        mov     dx,com_data     ; DX = data port address
        in      al,dx           ; read this character

        mov     bx,asc_in       ; get buffer pointer
        mov     [asc_buf+bx],al ; store this character
        inc     bx              ; bump pointer
        cmp     bx,bufsiz       ; time for wrap?
        jne     asc_int1        ; no, jump
        xor     bx,bx           ; yes,reset pointer

asc_int1:                       ; store updated pointer
        mov     asc_in,bx

        sti                     ; turn interrupts back on

        mov     al,20h          ; send EOI to 8259
        out     pic_eoi,al

        pop     ds              ; restore all registers
        pop     dx
        pop     bx
        pop     ax

        iret                    ; return from interrupt

asc_int endp

_TEXT   ends


_DATA   segment word public 'DATA'

in_char db      0               ; PC keyboard input char.
in_flag db      0               ; <>0 if char waiting

columns db      0               ; highest numbered column in
                                ; current display mode (39 or 79)

msg1    db      cr,lf
        db      'Display must be text mode.'
        db      cr,lf
msg1_len equ $-msg1

msg2    db      'Terminal emulator running...'
        db      cr,lf
msg2_len equ $-msg2

msg3    db      'Exit from terminal emulator.'
        db      cr,lf
msg3_len equ $-msg3

oldvec  dd      0               ; original contents of serial 
                                ; port interrupt vector

asc_in  dw      0               ; input pointer to ring buffer
asc_out dw      0               ; output pointer to ring buffer

asc_buf db      bufsiz dup (?)  ; communications buffer

_DATA   ends


STACK   segment para stack 'STACK'

        db      128 dup (?)

STACK   ends

        end     talk            ;  defines entry point  

[ RETURN TO DIRECTORY ]