; * * * * * * * * * * * * * * * version 2.9 * * * * * * * * * * * * * * * ; [36] Add calls to support Concurrent CP/M. ; [34] Make BREAK be correct length (250 ms). ; * * * * * * * * * * * * * * * version 2.7 * * * * * * * * * * * * * * * ; [30d] Add SET PORT command, currently unimplemented. ; [30c] Isolate all machine dependencies in KERIO. ; [30a] Add keyboard DEL key alteration for APC ; RonB, 04/18/84 ; * * * * * * * * * * * * * * * version 2.6 * * * * * * * * * * * * * * * ; [28e] Switch to local stack on interrupts. ; RonB, 03/28/84 ; * * * * * * * * * * * * * * * version 2.4 * * * * * * * * * * * * * * * ; [20b] Add PRTBRK to send break & set correct clock rate for NEC. ; [20d] Add a pseudo time-out to PRTOUT so it doesn't loop forever. ; RonB,03/02/84 ; [19a] Add XON/XOFF type flow control ; [19b] Clear screen and beginning and end of program. ; [19e] Add PRTBRK to send break to port (Rainbow only) ; [19g] Put in EQU for clock rate for timing loops. ; Rg, 2/84 <Oc.Garland%CU20B@Columbia-20> ; * * * * * * * * * * * * * * * version 2.3 * * * * * * * * * * * * * * * ; [par] Added calls to set parity, strip parity on input if ; other than none parity is called for. ; JD, 2/84 ; * * * * * * * * * * * * * * * version 2.2 * * * * * * * * * * * * * * * ; [2] Add a de-initialization routine for the serial port, to restore ; changed interrupt vectors, etc. ; RonB,12/23/83 ; [1] Add I/O support for the NEC Advanced Personal Computer ; RonB,12/23/83 ; * * * * * * * * * * * * * * * version 2.0 * * * * * * * * * * * * * * * ; This module contains all the low level communications port I/O ; routines. ; Here are the I/O routines for the NEC APC. CSEG $ ; Clock rate *10 for timing loops ;[19g] clckrt equ 49 ;[19g] 4.9 Mhz ;[20b] ; Interrupt vector locations, in data segment 0 mnioff equ 84h ;sio interrupt offset mniseg equ 86h ;sio interrupt segment ; 8259 Interrupt controller (master) iccmd equ 20h ;interrupt command register icmask equ 22h ;interrupt mask register ; 8259 commands and masks icEOI equ 20h ;end of interrupt (command) ictmof equ 08h ;disable timer (mask) icmnof equ 02h ;disable RS232 (mask) ; 8253-5 Interval Timer tmdata equ 2bh ;baud set (chan 1) tmcmd equ 2fh ;baud timer command port ; 8253 Timer commands tmch1 equ 76h ;select & init timer channel 1 ; 8251A USART controller mndata equ 30h ; data port mnsts1 equ 32h ;in status port mnsts2 equ 34h ;in nec special status port mncmd equ 32h ;out command port mnmsk equ 34h ;out interrupt mask port mntdc equ 36h ;out transmit disable port ; 8251 status port 1 bits mninp equ 02h ;receive ready value mnout equ 01h ;send ready value mndsr equ 80h ;data set ready ; 8251 status port 2 bits mncts equ 04h ;clear to send ; 8251 initialization instructions ; command instructions ctxe equ 01h ;transmit enable cdtr equ 02h ;dtr signal high crxe equ 04h ;receive enable cbrk equ 08h ;send break cerr equ 10h ;error reset crts equ 20h ;rts signal high cmode equ 40h ;reset - go to mode instruction format chunt equ 80h ;hunt for sync characters ; mode instructions m1x equ 01h ;baud rate factor: 1x m16x equ 02h ; 16x m64x equ 03h ; 64x m5d equ 00h ;data bits: 5 m6d equ 04h ; 6 m7d equ 08h ; 7 m8d equ 0Ch ; 8 mpn equ 00h ;parity: none mpo equ 10h ; odd mpe equ 30h ; even m1s equ 40h ;stop bits: 1 m15s equ 80h ; 1.5 m2s equ 0C0h ; 2 ; 8251 interrupt mask port bits txmsk equ 01h ;disable transmit complete interrupt rxmsk equ 02h ;disable receive complete interrupt tbemsk equ 04h ;disable transmit buffer empty interrupt outlmt EQU 1000H ;Number of times to check output status ; before giving up on send. ;[20d] ; dispatch: Under Concurrent CP/M, releases the processor for other ; use when waiting for either the receive or the send status ; routines to indicate success. dispatch: push ax push bx push cx mov cl,8Eh ; P-Dispatch int 224 pop cx pop bx pop ax ret ; Test if port is ready to send next char. Returns RSKP if ready. ; Trashes dx. outwt: cmp floctl, floxon ;are we doing flow-control? [19a] start jne outwt1 ;no - go on cmp xofrcv, true ;are we being held? je outwt3 ;yes - return status not ready outwt1: push ax mov dx,mnsts1 in al,dx and al,mndsr+mnout sub al,mndsr+mnout jnz outwt2 mov dx,mnsts2 in al,dx and al,mncts jnz outwt4 outwt2: pop ax outwt3: call dispatch ;let other CCPM processes have a chance ;[36] ret outwt4: pop ax jmp rskp ; Output data to port. Trashes DX and prints char in AL. outchr: mov dx,mndata out dx,al ret ; Output the character in AL, checking first to make sure the port is clear. prtout: call dopar ;[par] set parity push dx push cx ;[20d] begin mov cx,outlmt prtou2: call outwt ;Wait until the port is ready loop prtou2 ; or too much time has passed. nop call outchr ;Output it. pop cx ;[20d] end pop dx ret ; Test if data is available from port. instat: cmp mnchrn,0 ;Any chars in the buffer? jne inst2 call dispatch ;let other CCPM processes have a chance ;[36] ret inst2: jmp rskp ; Input data from port. Preserves all registers and returns char in ; AL. Gets the char from the ring buffer. Assumes a char is ; already there. inchr: push bx cli ;Disable interrupts while were are playing. dec mnchrn ;Decrement the number of chars in the buffer. mov bx,mnchop ;Get the pointer into the buffer. inc bx ;Increment to the next char. cmp bx,offset mnchrs+mnchnd ;Past the end? jb inchr2 mov bx, offset mnchrs ;If so wrap around to the start. inchr2: mov mnchop,bx ;Save the updated pointer. mov al,[bx] ;Get the character. sti ;All done, we can restore interrupts. pop bx cmp parflg,parnon ;[par] no parity? je inchr3 ;[par] yup, don't bother stripping and al,7fh ;[par] checking parity, strip off inchr3: cmp floctl, floxon ;do flow-control? [19a] start je inchr4 ;If yes jump ret inchr4: cmp xofsnt, true ;Have we sent an XOFF je inchr5 ;Jump if yes ret inchr5: cmp mnchrn, mntrg1 ;Under the low trigger point? jb inchr6 ;yes - jump ret inchr6: push ax ;save current character mov al, xon call prtout ;send an XON mov xofsnt, false ;turn off the flag pop ax ;get back character ret ; [19a] end mnax dw 0 ;Storage in CSEG ;[28e] begin mnsp dw 0 ; for use by interrupt handler mnsseg dw 0 mndseg dw 0 ; This routine handles the interrupts on input. mnint: cli mov cs:mnax, ax ;Save interrupt stack location. mov ax, sp mov cs:mnsp, ax mov ax, ss mov cs:mnsseg, ax mov ax, cs:mndseg ;Switch to our internal stack. mov ss, ax mov sp, offset mnstk push ds ;Save all registers. push es push bp push di push si push dx push cx push bx mov ds, ax ;Get our data segment address. call mnproc ;Process the character. mov dx, iccmd mov al, icEOI ;signal end of interrupt to controller out dx, al pop bx ;Restore all registers. pop cx pop dx pop si pop di pop bp pop es pop ds mov ax, cs:mnsp ;Restore the original stack. mov sp, ax mov ax, cs:mnsseg mov ss, ax mov ax, cs:mnax iret ;Return from the interrupt. ;[28e] end ; This routine (called by MNINT) gets a char from the serial port ; and puts it in the ring buffer. mnproc: mov dx,mnsts1 in al,dx ;Get the port status. and al,mninp ;Is a character waiting? jnz mnpro2 ; Yes, go take care of it. ret ; No, just a false alarm. mnpro2: mov dx,mndata in al,dx ;Read the char. cmp floctl, floxon ;are we doing flow-control ? [19a] start jne mnpr2b ;no - go on cmp al, xoff ;is it an XOFF? jne mnpr2a ;no - go on mov xofrcv, true ;set the flag ret mnpr2a: cmp al, xon ;an XON? jne mnpr2b ;no mov xofrcv, false ;clear the flag ret ; [19a] end mnpr2b: cmp mnchrn,mnchnd ;Is the buffer full? je mnperr ;If so, take care of the error. inc mnchrn ;Increment the character count. mov bx,mnchip ;Get the buffer input pointer. inc bx ;Increment it. cmp bx,offset mnchrs+mnchnd ;Past the end? jb mnpro3 mov bx, offset mnchrs ;Yes, point to the start again. mnpro3: mov mnchip,bx ;Save the pointer. mov [bx],al ;Put the character in the buffer. cmp floctl, floxon ;do flow-control? [19a] start je mnpro4 ;If yes jump ret mnpro4: cmp xofsnt, true ;Have we sent an XOFF jnz mnpro5 ret ;return if we have mnpro5: cmp mnchrn, mntrg2 ;Past the High trigger point? ja mnpro6 ;yes - jump ret mnpro6: mov al, xoff call prtout ;send an XOFF mov xofsnt, true ;set the flag ret ; [19a] End mnperr: ret ;Just return on an error for now. ; prtbrk - send a break ; [20b] start prtbrk: ; mov dx,mncmd ;break goes to command port mov al,cbrk+crts+cerr+crxe+cdtr+ctxe ;add break to normal command out dx,al mov ax, 275 ;.. for 275 millisec's [34] call mswait ; [34] mov al,crts+cerr+crxe+cdtr+ctxe ;RTS & DTR high, Rx & Tx enabled out dx,al ;return to normal setting ret ; [19e] end mswait: ; [34] start mov cx,5*clckrt ; inner loop count for 1 millisec. mswai1: sub cx,1 ;** inner loop takes 20 clock cycles jnz mswai1 ;** dec ax ; outer loop counter jnz mswait ; wait another millisecond ret ; [34] end ; serini - This routine initializes all devices that need it. ; Called at the start of the program. serini: cmp mninit,0FFh ; must only do this initialization once je serin2 mov mninit,0FFh push es ; Make DEL key return a 7F code rather than the 18 (control-X) ;[30a] begin ; that it ordinarily does. This involves modifying a key ; conversion table in the BIOS. Version 1.107 and later ; contain a pointer to this table at 40:256C, but earlier ; versions which do not are still in common use, so I will ; try to find it with a direct search. cld mov ax, 40h ;BIOS segment mov es, ax mov di, 2500h ;Start of BIOS mov al, 0FCh ;Key gives this value mov ah, 18h ;Translated to this value mov cx, -1 serina: repnz scasb ;Look for the key value or cx, cx ;Zero if search failed jz serinb ; If so, do nothing cmp es:[di], ah ;Translation value next? jne serina ; If not, keep looking mov kbpat, di ;Save the location we're patching. mov al, 7Fh ;Insert a DEL character mov es:[di], al serinb: ;...and continue ;[30a] end mov dx,icmask in al,dx ;get current interrupt mask mov mnxmsk,al ;save it for restore ; NEC recommends that the timer be turned off during interrupt-driven ; serial I/O, but this disables the clock and keyboard repeat. I have ; not had any bad results from leaving it enabled. I will leave the ; disabling code here in case something develops. -- RonB ; or al,ictmof+icmnof;mask off timer and sio interrupts or al,icmnof ;mask off sio interrupt out dx,al mov ax,ds ;save data segment in cseg mov cs:mndseg,ax ; for use by the interrupt handler mov ax,0 ;point to zero page to replace mov es,ax ;the sio interrupt vector mov ax,es:.mniseg ;after first saving the current vector mov mnxseg,ax mov ax,es:.mnioff mov mnxoff,ax cli mov ax,cs mov es:.mniseg,ax mov ax,offset mnint mov es:.mnioff,ax sti call stmode ;set mode & baud to defaults call stbaud mov dx,mntdc mov al,00h ;enable transmission of data out dx,al mov dx,mndata ;dummy read to clear buffer in al,dx mov dx,mnmsk mov al,txmsk+tbemsk ;set interrupt mask (enable read int) out dx,al mov dx,icmask in al,dx ;enable sio interrupts and al,not icmnof out dx,al pop es serin2: ret ; serfin - this routine is used to "undo" what serini has done, called ; just before exiting back to cp/m. serfin: cmp mninit,0FFh ;check if initialization has been done jne serfn2 ;if not, don't de-initialize mov mninit,0 push es ; Unpatch the keyboard conversion table ;[30a] begin ; Restore control-X value for DEL key from our 7F value. les di, dword ptr kbpat ;Get the patch location or di, di ;Did we patch it at all? jz serfia ; If not, skip this mov kbpat, 0 ;Show no longer patched mov al, 18h ;Restore control-X value mov es:[di], al serfia: ;...and continue ;[30a] end cli mov dx,icmask mov al,mnxmsk ;restore the old interrupt mask out dx,al mov ax,0 mov es,ax mov ax,mnxseg ;restore sio interrupt vector mov es:.mniseg,ax mov ax,mnxoff mov es:.mnioff,ax sti pop es serfn2: ret ; This routine clears the serial port input buffer. It is called to ; clear out excess NAKs that can result from server mode operation. cfibf: mov mnchrn, 0 ;Say no characters in the buffer. mov mnchip, OFFSET mnchrs-1+mnchnd ;Reset input pointer. mov mnchop, OFFSET mnchrs-1+mnchnd ;Reset output pointer. ret ; set the parity, number of data bits, and number of stop bits stmode: mov dx,mncmd mov al,0 ;recommended reset procedure: out dx,al ;three 0's followed by a cmode mov al,0 out dx,al mov al,0 out dx,al mov al,cmode ;enable mode setting out dx,al mov al,m1s ;1 stop, no parity, 8 data, 16x baud add al,mpn ;Note: these adds are distinct to add al,m8d ; allow the 8251 time to reset add al,m16x out dx,al mov al,crts+cerr+crxe+cdtr+ctxe ;RTS & DTR high, Rx & Tx enabled out dx,al ret ; set the baud rate stbaud: mov al,mnbaud ;get the baud rate information cmp al,12 ;check for valid range (0-12) ja stb02 mov bx,offset baudtb;get address of baud rate table add al,al ;compute word offset mov ah,0 add bx,ax mov dx,tmcmd mov al,tmch1 ;select timer channel 1 out dx,al mov dx,tmdata mov ax,[bx] ;get value out dx,al ;output low byte mov al,ah out dx,al ;output high byte stb02: ret dseg $ ; Serial port default parameters mnbaud db 6 ;300 baud ; Interval Timer values (assumes 16x baud rate mode) baudtb dw 0C00h ;50 baud 0 dw 0800h ;75 baud 1 dw 0600h ;100 baud 2 dw 0574h ;110 baud 3 dw 0400h ;150 baud 4 dw 0300h ;200 baud 5 dw 0200h ;300 baud 6 dw 0100h ;600 baud 7 dw 0080h ;1200 baud 8 dw 0040h ;2400 baud 9 dw 0020h ;4800 baud 10 dw 0010h ;9600 baud 11 dw 0008h ;19200 baud 12 mninit db 0 ;set to 0FFh if initialization has been done mnxmsk db 0 ;8259 interrupt mask storage mnxseg dw 0 ;system sio interrupt vector mnxoff dw 0 mnchnd equ 512 ;Size of circular buffer. mnchrs rb mnchnd ;Circular character buffer for input. mnchip dw mnchrs-1+mnchnd ;Input pointer into character buffer. mnchop dw mnchrs-1+mnchnd ;Output pointer into character buffer. mnchrn dw 0 ;Number of chars in the buffer. mntrg1 equ 128 ;[19a] Low trigger point for Auto XON/XOFF mntrg2 equ 384 ;[19a] High trigger point for Auto XON/XOFF floctl db 1 ;[19a] If floctl=floxon do Auto XON/XOFF logic xofsnt db 0 ;[19a] set if XOFF was sent xofrcv db 0 ;[19a] set if XOFF was recieved rw 32 ;Interrupt stack ;[28e] mnstk dw 0 ;bottom of stack ;[28e] kbpat dw 0000h,0040h ;patch location for DEL key ;[30a] CSEG $ ; The following routines do the SET and SHOW for the machine dependent ; features of Kermit. At present there are only two: baud rate setting ; and port selection. ; This is the SET BAUD rate subcommand bdset: mov dx, offset bdtab mov bx, offset bdhlp mov ah, cmkey call comnd jmp r mov temp1, bx mov ah, cmcfm call comnd ;Get a confirm. jmp r ; Didn't get a confirm. mov bx, temp1 mov mnbaud, bl ;Set the baud rate table index. call stbaud jmp rskp ; This is the SET PORT subcommand (not implemented in APC) prtset: mov ah, cmcfm call comnd ;Get a confirm. jmp $+3 ; Didn't get a confirm. mov dx, offset infms6 ;Tell user it's not implemented call tcrmsg jmp rskp ; The following procedures implement the SHOW command for the system ; dependent features of baud rate and port selection. shobd: mov dx, offset bdst ;Baud rate string. call tcrmsg mov al, mnbaud ;Print the keyword corresponding to the mov bx, offset bdtab ; current value of mnbaud. call tabprt ret shoprt: ret ;Port selection not implemented. DSEG $ bdtab db 13 ;Thirteen entries ;[6] begin db 3,'100$' dw 0002H db 3,'110$' dw 0003H db 4,'1200$' dw 0008H db 3,'150$' dw 0004H db 5,'19200$' dw 000CH db 3,'200$' dw 0005H db 4,'2400$' dw 0009H db 3,'300$' dw 0006H db 4,'4800$' dw 000AH db 2,'50$' dw 0000H db 3,'600$' dw 0007H db 2,'75$' dw 0001H db 4,'9600$' dw 000BH ;[6] end bdhlp db cr,lf,' 50 100 150 300 1200 4800 19200' db cr,lf,' 75 110 200 600 2400 9600$' ; The following routines do screen control. These are isolated here because ; the screen control sequences are likely to vary from system to system, even ; though the Rainbow and APC (the only systems implemented to date) both use ; ANSI sequences for this purpose. CSEG $ ; POSCUR - positions cursor to row and col (each 1 byte) pointed to by dx. poscur: mov bx, dx ;Do ANSI cursor positioning. mov cl, 10 mov al, [bx] ;Get row value sub ah, ah div cl ;units digit in ah, tens digit in al add ax, '00' ;Convert both to ASCII mov word ptr anspos+2, ax ;Save reversed (al,ah) mov al, 1[bx] ;Do same for column value sub ah, ah div cl add ax, '00' mov word ptr anspos+5, ax mov dx, offset anspos ;Print cursor positioning string. call tmsg ret ; CLRSCR - homes cursor and clears screen. clrscr: mov dx, offset apccls call tmsg ret ; CLRLIN - clears from cursor to end of line. clrlin: mov dl, cr ;Go to beginning of line call bout clreol: mov dx, offset ansclr ;Clear from cursor to end of line call tmsg ret ; REVON - turns on reverse video display revon: mov dx, offset ansron call tmsg ret ; REVOFF - turns off reverse video display revoff: mov dx, offset ansrof call tmsg ret ; BLDON - turns on bold (highlighted) display bldon: ret ; BLDOFF - turns off bold (highlighted) display bldoff: ret DSEG $ anspos db esc,'[00;00H$' ;Position cursor to row and column apccls db 1Eh,1Ah,'$' ;Home cursor and clear screen ansclr db esc,'[K$' ;Clear from cursor to end of line ansron db esc,'[7m$' ;Turn on reverse video ansrof db esc,'[m$' ;Turn off reverse video ; Here tab expansion is done if necessary. If not, just return retskp. cseg $ dotab: cmp dl, tab ;A tab? je dotab1 jmp rskp ;No, just proceed. dotab1: mov curbuf, 2 ;Report cursor position command. mov dx, offset curbuf mov cl, 7 int 220 ;Special BIOS function. mov al, curbuf+2 ;Column position in binary (0-79). and al, 07h ;Number of spaces needed mov cx, 0008h ; = 8 - (col mod 8) sub cl, al dotab2: push cx mov dl, ' ' call dbout2 pop cx loop dotab2 ret dseg $ curbuf db 2,0,0 ;command, row, column of cursor position ;[7] delstr db 10O,10O,'$' ;Delete string. system db ' NEC Advanced Personal Computer$' ;[1][20a]