; Kermit system dependent module for IBM-PC ; Edit History ; Make break be 275 ms, DT 5:51pm Thursday, 6 December 1984 public serini, serrst, clrbuf, outchr, coms, vts, dodel, public ctlu, cmblnk, locate, prtchr, dobaud, clearl, public dodisk, getbaud, beep, public count, xofsnt, puthlp, putmod, clrmod, poscur public sendbr, machnam, setktab, setkhlp, lclini, showkey include msdefs.h false equ 0 true equ 1 mntrgh equ bufsiz*3/4 ; High point = 3/4 of buffer full. ; constants used by serial port handler BRKBIT EQU 040H ; Send-break bit. TIMER EQU 40H ; Use to issue short beep. PORT_B EQU 61H ; Port B address. MCONF EQU 11H ; Machine configuration. KEYB EQU 16H BIOS EQU 10H MDMDAT1 EQU 03F8H ; Address of modem port (data). [19b] MDMSTS1 EQU 03FDH ; Address of modem port status. [19b] MDMCOM1 EQU 03FBH ; Address of modem port command. [19b] MDMDAT2 EQU 02F8H ; Port 2 address. [19b] MDMSTS2 EQU 02FDH ; Port 2 status. [19b] MDMCOM2 EQU 02FBH ; Port 2 command. [19b] MDMINP EQU 1 ; Input ready bit. MDMINTV EQU 0030H ; Address of modem port interrupt vector. MDINTV2 EQU 002CH ; Address for port 2. [19b] MDMINTO EQU 0EFH ; Mask to enable interrupt for modem port. MDINTO2 EQU 0F7H ; Enable interrupt level 3. [19b] MDMINTC EQU 010H ; Bit to set to disable interrupts for modem. MDINTC2 EQU 008H ; Disable IRQ3. [19b] INTCONT EQU 0021H ; Address of 8259 interrupt controller ICW2-3. INTCON1 EQU 0020H ; Address of 8259 ICW1. EOICOM EQU 0064H ; End of interrupt. EOICOM2 EQU 0063H ; End of interrupt for COM2. [19b] ; external variables used: ; drives - # of disk drives on system ; flags - global flags as per flginfo structure defined in pcdefs ; trans - global transmission parameters, trinfo struct defined in pcdefs ; portval - pointer to current portinfo structure (currently either port1 ; or port2) ; port1, port2 - portinfo structures for the corresponding ports ; global variables defined in this module: ; xofsnt, xofrcv - tell whether we saw or sent an xoff. ; setktab - keyword table for redefining keys (should contain a 0 if ; not implemented) ; setkhlp - help for setktab. datas segment public 'datas' extrn drives:byte,flags:byte, trans:byte extrn portval:word, port1:byte, port2:byte setktab db 12 mkeyw 'BACKSPACE',0eh mkeyw 'F1',3bh mkeyw 'F2',3ch mkeyw 'F3',3dh mkeyw 'F4',3eh mkeyw 'F5',3fh mkeyw 'F6',40h mkeyw 'F7',41h mkeyw 'F8',42h mkeyw 'F9',43h mkeyw 'F10',44h mkeyw 'SCAN',-1 setkhlp db cr,lf,'Keyname: backspace, f1, ... f10, or "SCAN" follwed by ' db 'decimal scan code$' brkval db 0 ; What to send for a break. brkadr dw 0 ; Where to send it. modem mdminfo erms20 db cr,lf,'?Warning: System has no disk drives$' ; [21a] erms40 db cr,lf,'?Warning: Unrecognized baud rate$' badbd db cr,lf,'Unimplemented baud rate$' machnam db 'IBM-PC$' crlf db cr,lf,'$' delstr db BS,' ',BS,'$' ; Delete string. [21d] clrlin db cr,'$' ; Clear line (just the cr part). savsci dw ? ; Save for serial port interrupt vector. [14] savscs dw ? ; Ditto. [14] savbr1 dw ? ; "Break" interrupt vector. [25] savbr2 dw ? ; Ditto. [25] portin db 0 ; Has comm port been initialized. [21c] xofsnt db 0 ; Say if we sent an XOFF. xofrcv db 0 ; Say if we received an XOFF. tmp db ?,'$' temp dw 0 temp1 dw ? ; Temporary storage. temp2 dw ? ; Temporary storage. ontab db 02H ; Two entries. db 03H,'OFF$' ; Should be alphabetized. [19a] dw 00H db 02H,'ON$' dw 01H comptab db 04H db 01H,'1$' dw 01H db 01H,'2$' dw 00H db 04H,'COM1$' dw 01H db 04H,'COM2$' dw 00H ; this table is indexed by the baud rate definitions given in ; pcdefs. Unsupported baud rates should contain FF. bddat label word dw 0FFH ; 45.5 baud -- Not supported. dw 900H ; 50 baud dw 600H ; 75 baud dw 417H ; 110 baud dw 359H ; 134.5 baud dw 300H ; 150 baud dw 180H ; 300 baud dw 0C0H ; 600 baud dw 60H ; 1200 baud dw 40H ; 1800 baud dw 3AH ; 2000 baud dw 30H ; 2400 baud dw 18H ; 4800 baud dw 0CH ; 9600 baud dw 06H ; 19200 baud -- Not supported. dw 03H ; 38400 baud -- Not supported. ; 19200 WORKS WELL -- DON'T KNOW ABOUT 38400 BUT SHOULD ALSO WORK ; WELL. THEREFORE LEAVE THEM IN. - GCE. ; variables for serial interrupt handler source db bufsiz DUP(?) ; Buffer for data from port. srcpnt dw 0 ; Pointer in buffer (DI). count dw 0 ; Number of chars in int buffer. savesi dw 0 ; Save SI register here. telflg db 0 ; Are we acting as a terminal. mst dw 0 ; Modem status address. mdat dw 0 ; Modem data address. mdeoi db 0 ; End-of-Interrupt value. rbtrn db 7fH ; rubout shkbuf db 300 dup (?) ; room for definition shkmsg db ' Scan code: ' shkmln equ $-shkmsg shkms1 db cr,lf,' Definition: ' shkm1ln equ $-shkms1 datas ends code segment public extrn comnd:near, dopar:near, defkey:near, gss:near assume cs:code,ds:datas ; local initialization lclini proc near mov ax,0eH ; scan code for arrow key mov si,offset rbtrn ; translate to rubout mov cx,1 ; one char translation call defkey mov brkval,BRKBIT ; What to send for a break. mov ax,modem.mdcom ; Where to send it. mov brkadr,ax ret lclini endp ; this is called by Kermit initialization. It checks the ; number of disks on the system, sets the drives variable ; appropriately. Returns normally. DODISK PROC NEAR int mconf ; Get equipment configuration. mov ah,al ; Store AL value for a bit. and al,01H ; First, look at bit 0. jz dodsk0 ; No disk drives -- forget it. mov al,ah ; Get back original value. mov cl,6 ; Shift over bits 6 and 7. shr al,cl ; To positions 0 and 1. inc al ; Want 1 thru 4 (not 0 thru 3). mov drives,al ; Remember how many. ret dodsk0: mov ah,prstr ; Print a warning message. mov dx,offset erms20 ; I'm not sure if things will int dos ; work with only a cassette. mov drives,0 ; Say there aren't any drives. ret DODISK ENDP ; show the definition of a key. The terminal argument block (which contains ; the address and length of the definition tables) is passed in ax. ; Returns a string to print in AX, length of same in CX. ; Returns normally. showkey proc near push es push ax ; save the ptr mov bx,ds mov es,bx ; address data segment cld showk1: xor ah,ah int keyb ; read a char push ax ; save the character call gss ; get shift state pop bx mov ah,al ; shift state to ah mov al,bh ; scan code to al push ax ; remember scan code mov di,offset shkbuf mov si,offset shkmsg mov cx,shkmln rep movsb ; copy in initial message call nout ; write out scan code mov si,offset shkms1 mov cx,shkm1ln ; second message rep movsb pop ax ; get scan code back pop bx ; and terminal arg block mov cx,[bx].klen ; and length jcxz showk2 ; no table, not defined push di ; remember output ptr mov di,[bx].ktab ; get key table repne scasw ; search for a definition for this mov si,di ; remember result ptr pop di ; get output ptr back jne showk2 ; not defined, forget it sub si,[bx].ktab ; compute offset from beginning sub si,2 ; minus 2 for pre-increment add si,[bx].krpl ; get index into replacement table mov si,[si] ; pick up replacement mov cl,[si] ; get length mov ch,0 inc si rep movsb ; copy into buffer showk2: mov ax,offset shkbuf ; this is buffer mov cx,di sub cx,ax ; length pop es ret ; and return showkey endp ; Clear the input buffer. This throws away all the characters in the ; serial interrupt buffer. This is particularly important when ; talking to servers, since NAKs can accumulate in the buffer. ; Returns normally. CLRBUF PROC NEAR cli mov ax,offset source mov srcpnt,ax mov savesi,ax mov count,0 sti ret CLRBUF ENDP ; Clear to the end of the current line. Returns normally. CLEARL PROC NEAR mov ah,3 ; Clear to end of line. mov bh,0 int bios ; Get current cursor position mov cx,dx mov dl,79 mov ah,7 mov al,0 mov bh,7 int bios ret CLEARL ENDP ; Put the char in AH to the serial port. This assumes the ; port has been initialized. Should honor xon/xoff. Skip returns on ; success, returns normally if the character cannot be written. outchr: mov bp,portval cmp ds:[bp].floflg,0 ; Are we doing flow control. je outch2 ; No, just continue. xor cx,cx ; clear counter outch1: cmp xofrcv,true ; Are we being held? jne outch2 ; No - it's OK to go on. loop outch1 ; held, try for a while mov xofrcv,false ; timed out, force it off and fall thru. outch2: push dx ; Save register. sub cx,cx mov al,ah ; Parity routine works on AL. call dopar ; Set parity appropriately. mov ah,al ; Don't overwrite character with status. mov dx,modem.mdstat ; Get port status. outch3: in al,dx test al,20H ; Transmitter ready? jnz outch4 ; Yes loop outch3 jmp outch5 ; Timeout outch4: mov al,ah ; Now send it out mov dx,modem.mddat out dx,al pop dx jmp rskp outch5: pop dx ret ; This routine blanks the screen. Returns normally. CMBLNK PROC NEAR ; This is stolen from the IBM example. mov cx,0 mov dx,184FH mov bh,7 mov ax,600H int bios ret CMBLNK ENDP ; Locate: homes the cursor. Returns normally. LOCATE PROC NEAR mov dx,0 ; Go to top left corner of screen. jmp poscur LOCATE ENDP ; write a line in inverse video at the bottom of the screen... ; the line is passed in dx, terminated by a $. Returns normally. putmod proc near push dx ; preserve message mov cx,1800h mov dx,184fh mov ax,600h ; scroll to clear the line mov bh,70h ; inverse video int bios mov dx,1800h ; now address line 24 call poscur pop dx ; get message back mov ah,prstr int dos ; write it out ret ; and return putmod endp ; clear the mode line written by putmod. Returns normally. clrmod proc near mov cx,1800h mov dx,184fh mov ax,600h mov bh,7h int bios ret clrmod endp ; put a help message on the screen. This one uses reverse video... ; pass the message in ax, terminated by a null. Returns normally. puthlp proc near push ax ; preserve this mov si,ax ; point to it mov dh,1 ; init counter puthl1: lodsb ; get a byte cmp al,lf ; linefeed? jne puthl2 ; no, keep going inc dh ; count it jmp puthl1 ; and keep looping puthl2: cmp al,0 ; end of string? jne puthl1 ; no, keep going mov ax,600h ; scroll to clear window xor cx,cx ; from top left mov dl,4fh ; to bottom right of needed piece mov bh,70h ; inverse video int bios call locate ; home cursor pop si ; point to string again puthl3: lodsb ; get a byte cmp al,0 ; end of string? je puthl4 ; yes, stop mov ah,14 int bios ; else write to screen jmp puthl3 ; and keep going puthl4: mov dx,24 * 100H ; go to last line jmp poscur ; position and return puthlp endp ; Set the baud rate for the current port, based on the value ; in the portinfo structure. Returns normally. DOBAUD PROC NEAR mov bp,portval mov temp1,ax ; Don't overwrite previous rate. [25] mov ax,ds:[bp].baud ; Check if new rate is valid. [25] mov tmp,2 mul tmp ; Get index into baud table. mov bx,offset bddat ; Start of table. add bx,ax mov ax,[bx] ; The data to output to port. cmp ax,0FFH ; Unimplemented baud rate. jne dobd0 mov ax,temp1 ; Get back orginal value. mov ds:[bp].baud,ax ; Leave baud rate as is. mov ah,prstr mov dx,offset badbd ; Give an error message. int dos ret dobd0: mov temp1,ax ; Remember value to output. [25] mov dx,modem.mdcom ; LCR -- Initialize baud rate. [19b] in al,dx mov bl,al or ax,80H out dx,al mov dx,modem.mddat ; [19b] mov ax,temp1 out dx,al inc dx mov al,ah out dx,al mov dx,modem.mdcom ; [19b] mov al,bl out dx,al ret DOBAUD ENDP ; Get the current baud rate from the serial card and set it ; in the portinfo structure for the current port. Returns normally. ; This is used during initialization. GETBAUD PROC NEAR mov dx,modem.mdcom ; Get current Line Control Register value. in al,dx mov bl,al ; Save it. or ax,80H ; Turn on to access baud rate generator. out dx,al mov dx,modem.mddat ; Divisor latch. inc dx in al,dx ; Get hi order byte. mov ah,al ; Save here. dec dx in al,dx ; Get lo order byte. push ax mov dx,modem.mdcom mov al,bl ; Restore old value. out dx,al pop ax cmp ax,0FFFFH ; Who knows what this is. je getb2 mov bx,offset bddat ; Find rate's offset into table. mov cl,0 ; Keep track of index. getb0: cmp ax,[bx] je getb1 inc cl cmp cl,baudsiz ; At the end of the list. jge getb2 add bx,2 jmp getb0 getb1: mov ch,0 mov bp,portval mov ds:[bp].baud,cx ; Set baud rate. ret getb2: mov ah,prstr mov dx,offset erms40 int dos ret GETBAUD ENDP ; skip returns if no character available at port, ; otherwise returns with char in al, # of chars in buffer in dx. PRTCHR PROC NEAR call chkxon ; see if we need to xon cmp count,0 jnz prtch2 jmp rskp ; No data - check console. prtch2: mov si,savesi lodsb ; get a byte cmp si,offset source + bufsiz ; bigger than buffer? jb prtch1 ; no, keep going mov si,offset source ; yes, wrap around prtch1: dec count mov savesi,si mov dx,count ; return # of chars in buffer ret PRTCHR ENDP ; local routine to see if we have to transmit an xon chkxon proc near push bx mov bx,portval cmp [bx].floflg,0 ; doing flow control? je chkxo1 ; no, skip all this cmp xofsnt,false ; have we sent an xoff? je chkxo1 ; no, forget it cmp count,mntrgh ; below trigger? jae chkxo1 ; no, forget it mov ax,[bx].flowc ; ah gets xon call outchr ; send it nop nop nop ; in case it skips mov xofsnt,false ; remember we've sent the xon. chkxo1: pop bx ; restore register ret ; and return chkxon endp ; Send a break out the current serial port. Returns normally. SENDBR PROC NEAR push cx push dx push ax xor cx,cx ; Clear loop counter. mov dx,brkadr ; Port address. [19b] in al,dx ; Get current setting. or al,brkval ; Set send-break bit(s). out dx,al ; Start the break. push ax mov ax,275 ; # of ms to wait call pcwait ; hold break for desired interval pop ax xor al,brkval ; Clear send-break bit(s). out dx,al ; Stop the break. pop ax pop dx pop cx ret ; And return. SENDBR ENDP ; Wait for the # of milliseconds in ax ; Thanks to Bernie Eiben for this one. pcwait proc near mov cx,240 ; inner loop counter for 1 millisecond pcwai1: sub cx,1 ; inner loop takes 20 clock cycles jnz pcwai1 dec ax ; outer loop counter jnz pcwait ; wait another millisecond ret pcwait endp ; Position the cursor according to contents of DX: ; DH contains row, DL contains column. Returns normally. POSCUR PROC NEAR mov ah,2 ; Position cursor. mov bh,0 int bios ret POSCUR ENDP ; Delete a character from the terminal. This works by printing ; backspaces and spaces. Returns normally. DODEL PROC NEAR mov ah,prstr mov dx,offset delstr ; Erase weird character. int dos ret DODEL ENDP ; Move the cursor to the left margin, then clear to end of line. ; Returns normally. CTLU PROC NEAR mov ah,prstr mov dx,offset clrlin int dos call clearl ret CTLU ENDP ; set the current port. COMS PROC NEAR mov dx,offset comptab mov bx,0 mov ah,cmkey call comnd jmp r push bx mov ah,cmcfm call comnd ; Get a confirm. jmp comx ; Didn't get a confirm. nop pop bx mov flags.comflg,bl ; Set the comm port flag. cmp flags.comflg,1 ; Using Com 1? jne coms0 ; Nope. mov ax,offset port1 mov portval,ax mov modem.mddat,MDMDAT1 ; Set COM1 defaults. mov modem.mdstat,MDMSTS1 mov modem.mdcom,MDMCOM1 mov modem.mddis,MDMINTC mov modem.mden,MDMINTO mov modem.mdmeoi,EOICOM mov modem.mdintv,MDMINTV mov brkadr,MDMCOM1 ret coms0: mov ax,offset port2 mov portval,ax mov modem.mddat,MDMDAT2 ; Set COM2 defaults. mov modem.mdstat,MDMSTS2 mov modem.mdcom,MDMCOM2 mov modem.mddis,MDINTC2 mov modem.mden,MDINTO2 mov modem.mdmeoi,EOICOM2 mov modem.mdintv,MDINTV2 mov brkadr,MDMCOM2 ret comx: pop bx ret COMS ENDP ; Set heath emulation on/off. VTS PROC NEAR mov dx,offset ontab mov bx,0 mov ah,cmkey call comnd jmp r push bx mov ah,cmcfm call comnd ; Get a confirm. jmp vt0 ; Didn't get a confirm. nop pop bx mov flags.vtflg,bl ; Set the VT52 emulation flag. ret vt0: pop bx ret VTS ENDP ; initialization for using serial port. This routine performs ; any initialization necessary for using the serial port, including ; setting up interrupt routines, setting buffer pointers, etc. ; Doing this twice in a row should be harmless (this version checks ; a flag and returns if initialization has already been done). ; SERRST below should restore any interrupt vectors that this changes. ; Returns normally. SERINI PROC NEAR push es cmp portin,0 ; Did we initialize port already? [21c] jne serin0 ; Yes, so just leave. [21c] cli ; Disable interrupts cld ; Do increments in string operations xor ax,ax ; Address low memory mov es,ax mov bx,modem.mdintv ; Save serial card interrupt vector. [19b] mov ax,es:[bx] mov savsci,ax mov ax,offset serint ; And point it to my routine mov es:[bx],ax add bx,2 ; Save CS register too. [19b] mov ax,es:[bx] mov savscs,ax mov es:[bx],cs mov portin,1 ; Remember port has been initialize. call clrbuf ; Clear input buffer. mov ax,modem.mdstat mov mst,ax ; Use this address for status. mov ax,modem.mddat mov mdat,ax ; Use this address for data. mov al,modem.mdmeoi mov mdeoi,al ; Use to signify end-of-interrupt. in al,21H ; Set up 8259 interrupt controller and al,modem.mden ; Enable INT3 or INT4. out 21H,al mov dx,modem.mdcom ; Set up the serial card. mov al,3 out dx,al mov dl,0F9H mov al,1 ; Set up interrupt enable register out dx,al mov dl,0FCH ; Enable interrupts from serial card mov al,0BH out dx,al sti ; Allow interrupts mov dl,0F8H in al,dx serin0: pop es ret ; We're done. SERINI ENDP ; Reset the serial port. This is the opposite of serini. Calling ; this twice without intervening calls to serini should be harmless. ; Returns normally. SERRST PROC NEAR push es ; preserve this cmp portin,0 ; Reset already? je srst1 ; Yes, just leave. cli ; Disable interrupts mov dx,03FCH ; Disable modem interrupts cmp flags.comflg,1 ; Using port 1 ? je srst0 ; Yes - continue. mov dh,02 ; Set for port 2. srst0: mov al,3 out dx,al in al,21H ; Interrupt controller or al,modem.mddis ; Inhibit IRQ3 or IRQ4. out 21H,al xor bx,bx ; Address low memory mov es,bx mov bx,modem.mdintv ; Restore the serial card int vector mov ax,savsci mov es:[bx],ax add bx,2 ; Restore CS too. mov ax,savscs mov es:[bx],ax mov portin,0 ; Reset flag. sti srst1: pop es ret ; All done. SERRST ENDP ; serial port interrupt routine. This is not accessible outside this ; module, handles serial port receiver interrupts. SERINT PROC NEAR push bx push dx push ax push es push di push ds push bp push cx cld mov ax,seg datas mov ds,ax ; address data segment mov es,ax mov di,srcpnt ; Registers for storing data. mov dx,mst ; Asynch status port. [19b] in al,dx test al,mdminp ; Data available? jz retint ; Nope. mov dx,mdat ; [19b] in al,dx cmp telflg,0 ; File transfer or terminal mode? [17c] jz srint0 and al,7FH ; Terminal mode (7 bits only). srint0: or al,al jz retint ; Ignore nulls. mov ah,al and ah,7fH ; strip parity temporarily cmp ah,7FH ; Ignore rubouts, too. jz retint mov bp,portval cmp ds:[bp].floflg,0 ; Doing flow control? je srint2 ; Nope. mov bx,ds:[bp].flowc ; Flow control char (BH = XON, BL = XOFF). cmp al,bl ; Is it an XOFF? jne srint1 ; Nope, go on. mov xofrcv,true ; Set the flag. jmp retint srint1: cmp al,bh ; Get an XON? jne srint2 ; No, go on. mov xofrcv,false ; Clear our flag. jmp retint srint2: stosb cmp di,offset source + bufsiz jb srint3 ; not past end... mov di,offset source ; wrap buffer around srint3: inc count cmp ds:[bp].floflg,0 ; Doing flow control? je retint ; No, just leave. cmp xofsnt,true ; Have we sent an XOFF? je retint ; Yes. cmp count,mntrgh ; Past the high trigger point? jbe retint ; No, we're within our limit. mov ah,bl ; Get the XOFF. call outchr ; Send it. nop nop nop ; ignore failure. mov xofsnt,true ; Remember we sent it. retint: mov srcpnt,di sti mov al,mdeoi ; [19b] out intcon1,al ; Send End-of-Interrupt to 8259. pop cx pop bp pop ds pop di pop es pop ax pop dx pop bx intret: iret SERINT ENDP ; Produce a short beep. The PC DOS bell is long enough to cause a loss ; of data at the port. Returns normally. BEEP PROC NEAR mov al,10110110B ; Gen a short beep (long one losses data.) out timer+3,al ; Code snarfed from Technical Reference. mov ax,533H out timer+2,al mov al,ah out timer+2,al in al,port_b mov ah,al or al,03 out port_b,al sub cx,cx mov bl,1 beep0: loop beep0 dec bl jnz beep0 mov al,ah out port_b,al ret BEEP ENDP ; put the number in ax into the buffer pointed to by di. Di is updated nout proc near mov dx,0 ; high order is always 0. mov bx,10 div bx ; divide to get digit push dx ; save remainder digit or ax,ax ; test quotient jz nout1 ; zero, no more of number call nout ; else call for rest of number nout1: pop ax ; get digit back add al,'0' ; make printable stosb ; drop it off ret ; and return nout endp ; Jumping to this location is like retskp. It assumes the instruction ; after the call is a jmp addr. RSKP PROC NEAR pop bp add bp,3 push bp ret RSKP ENDP ; Jumping here is the same as a ret. R PROC NEAR ret R ENDP code ends end