title Fido Drivers for IBM ; ;Fido standard Async and timer tick driver ;for IBM et al. Handles 8250/16450/16550 ;chips, and internal vs. FOSSIL interfaces. ; ;T. Jennings 4 Dec 91 ; ;init_async(port); ;int port; ;Initialize the async system. If a FOSSIL driver ;is detected, it is used, otherwise the integral ;driver is installed. ; ; ;init_tick(); Install the time tick ; functions. After this call ; the counters should start ; incrementing. ; ;uninit_tick(); Remove the time circuit. ; ; h= set_tick(&timer); ; int h; ; long *timer; ; ; Returns the handle of an allocated ;timer tick; the counter will be continuously ;incremented until the handle is released. ;The timer has a resolution of 1 mS, and ;unknown accuracy. It is generally incremented ;in increments of the system clock. -1 is ;returned if no handles are available. ; ; clr_tick(h); ; int h; ; ; Release a handle. ; include mc.ash .model small ; ;-------------------------------- ; .data fossil db 0 ;1 == FOSSIL installed ; ;Timer tick data. ; MAXTICK equ 16 ;max. timer handles ticks dw 0 ;work counter intno db (?) ;interrupt number tickint dw (?) ;how often pile dw MAXTICK dup (0) ;comma table of ; ;Async data. ; ioport dw 0 ;channel/device number mdmbits db 0 ;copy of DTR, RTS IN_SIZE equ 400 ;buffer size in_beg db IN_SIZE dup (?) ;input buffer in_end db 0 in_count dw 0 ;chars in the buffer in_head dw 0 in_tail dw 0 OUT_SIZE equ 300 ;buffer size out_beg db OUT_SIZE dup (?) out_end db 0 out_count dw 0 ;chars in the buffer out_head dw 0 out_tail dw 0 ; ;Baud rate divisor table. This contains ;baud rate, the divisor (for integral driver) ;and the AL value for the FOSSIL call. ; baudtbl label word dw 300,384,01000011b dw 600,192,01100011b dw 1200,96,10000011b dw 2400,48,10100011b dw 4800,24,11000011b dw 9600,12,11100011b dw 19200,6,00000011b dw 38400,3,00100011b dw 0,0,0 ; ; 8250 hardware configuration ; base dw 3f8h ; COM1 base port intnum db 12 ; COM1 interrupt intmsk db 11101111b ; MDATA equ 0 ; modem data port MLINE equ 3 ; line control MLSTAT equ 5 ; line status MCNTRL equ 4 ; modem control MSTAT equ 6 ; modem status MBAUDL equ 0 ; baud rate low MBAUDH equ 1 ; baud rate high INTENB equ 1 ; interrupt enable register INTID equ 2 ; interrupt ID FIFO equ 2 ; 16550 FIFO control DLAB equ 80h ; baud reg. access bit RDA equ 01h ; character avail TBE equ 20h ; buffer empty MODE equ 03h ; 8 bits, no parity MMODE equ 00001000b ; enable interrupt RTS equ 2 ; RTS bit DTR equ 1 ; DTR bit, CTS equ 10h ; CTS bit, B300 equ 0180h ; 300 baud B1200 equ 0060h ; 1200 baud B9600 equ 000ch ; 9600 baud ; ;Interrupt enable bits. ; RDAINT equ 0001b ; Rx interrupt TBEINT equ 0010b ; Tx interrupt RLSINT equ 0100b ; line status MDMINT equ 1000b ; modem status page .code ; ;These are the timer tick parameters ;from the driver. They must be in CS. ; dataseg dw (?) ;DS: value for ISRs oldvec dd (?) ;saved tick vector, int14vec dd 0 ;INT 14h vector ; ;init_async(port); ;int port; ; ;If a FOSSIL driver is installed, use it, ;otherwise install ours and init the hardware. ; func _init_async mov ax,arg0 ;remember port # mov ioport,ax call fossil_setup ;detect FOSSIL test fossil,1 ;if there jz in0 ;done! jmp inz in0: mov intnum,12 ;defaults for mov intmsk,11101111b;COM1 mov base,3f8h cmp word ptr arg0,0 ;port # je in1 mov intnum,11 ;COM2 mov intmsk,11110111b mov base,2f8h in1: mov al,intnum mov dx,offset i8250 mov cs:dataseg,ds ;store DS push ds mov cx,cs ;set DS to CS, mov ds,cx ;set Rx int vec call setvec pop ds ; ;Special code for the buggy 8250 A version. ; mov bx,1 ;set fast baud call setbaud mov dx,base add dx,MDATA ;then send a char mov al,0 call outp mov cx,-1 ;delay a bit in2: jmp $ + 2 ;yucky poo loop in2 ; ;Enable the 16550 FIFO and clear them. ; mov dx,base add dx,FIFO mov al,00000111b ;on 8250, call outp ;writes IIR pushf cli mov mdmbits,MMODE + RTS call __mflush ;buffers empty mov dx,base add dx,MLINE ;reset the DLAB mov al,MODE ;bit, set 8 data call outp ;bits, no parity, mov dx,base add dx,INTENB ;enable interrupt mov al,RDAINT + MDMINT;receive only! call outp call _raise_dtr ;raise DTR, mov dx,base add dx,MDATA ;clear chars call inp ;in the UART, call inp call inp call inp mov dx,base add dx,MLSTAT ;clear errors, etc call inp mov dx,base add dx,MSTAT ;just for the hell of it, call inp ;clear delta CTS, etc mov dx,21h call inp and al,intmsk ;clear com interrupt bit call outp popf inz: endf _init_async ; ;Local routine: If a FOSSIL driver is installed, ;initialize it, flag it, and yank the vector ;so we can do a long call instead of a miserable ;Intel INT. ; fossil_setup: test fossil,1 ;if already checked jnz fsz ;don't repeat mov dx,ioport ;DX= port # mov bx,0 ;BX= (not 4f50) mov ah,4 ;AH= init int 14h ;do it cmp ax,1954h ;present? jne fsz push es ;yup, mov al,14h ;get INT 14h call getvec ;vector mov word ptr cs:int14vec,bx ;save it, mov word ptr cs:int14vec + 2,es pop es mov fossil,1 ;yup, flag it fsz: ret ; ;This disgusting bit of code calls the saved ;INT 14h vector instead of using the sluggish ;INT xx isntruction. ; int14: mov dx,ioport ;all calls pushf ;setup for IRET call dword ptr cs:[int14vec] ret ; ;uninit_async(); ; ;If a FOSSIL driver was used, de-init it. ; func _uninit_async test fossil,1 jz ui1 ;if FOSSIL used mov ah,5 ;AH= Deinit call int14 ;call FOSSIL jmp short uiz ui1: pushf cli ; ;Turn off RDA & TBE interrupt sources in ;the 8250. ; mov dx,base add dx,INTENB ;reset RDA/TBE mov al,0 ;in 8250, call outp ; ;Leave DTR and RTS high and IRQx as-is. ; mov dx,base add dx,MCNTRL mov al,DTR + RTS and al,mdmbits call outp ; ;Disable the 8250 interrupt at the 8259A ; mov dx,21h call inp ;reset 8259 msk mov ah,intmsk xor ah,11111111b or al,ah call outp ; ;Disable the FIFO. ; mov dx,base add dx,FIFO mov al,0 ;on 8250, call outp ;writes IIR popf ; ;Clear the 'fossil' flag, to allow init again. ; uiz: mov fossil,0 endf _uninit_async page ; ;Set the baud rate as specified. Return the ;baud rate, or zero if an illegal one was ;specified. ; func _baud mov si,offset baudtbl b1: mov ax,[si] ;AX= baud mov bx,[si + 2] ;BX= divisor mov cx,[si + 4] ;CL= FOSSIL val add si,6 or ax,ax ;if zero, quit je bz cmp ax,arg0 ;correct one? jne b1 call setbaud ;set it, mov ax,arg0 ;return baud bz: endf _baud ; ;Set the baud rate, the divisor in BX and the ;FOSSIL call value in CL. ; setbaud: test fossil,1 ;check for FOSSIL jz sb1 mov ah,0 ;AH= Set Baud mov al,cl ;AL= bits call int14 ret sb1: pushf cli mov dx,base add dx,MLINE ;select baud rate port mov al,DLAB+MODE ;set DLAB, 8 bits, call outp mov dx,base add dx,MBAUDL ;low byte of baud mov al,bl call outp mov dx,base add dx,MBAUDH ;now high byte mov al,bh call outp mov dx,base add dx,MLINE ;reset DLAB, mov al,MODE call outp ;select data ports popf ret page ; ;Start or end a line break. Assumes that no ;characters will be sent after break is ;started and that it lasts at least two char ;times. ; func __mbreak mov ax,arg0 ;AL == flag test fossil,1 jz mk1 mov ah,1ah ;AH= Break call int14 jmp short mkz mk1: cmp ax,0 je mk3 mov al,0 ;set break call outa ;send a null mk2: cmp out_count,0 ;wait til sent jnz mk2 mov dx,base add dx,MLINE ;cause a break mov al,MODE + 40h call outp jmp mkz mk3: mov dx,base ;reset break add dx,MLINE mov al,MODE ;set normal mode call outp mkz: endf __mbreak ; ;Lower DTR. RTS and IRQx are in the port, so ;we have to preserve them. ; func _lower_dtr test fossil,1 jz ld1 mov ah,6 ;AH= DTR mov al,0 ;AL= lower DTR call int14 jmp short ldz ld1: mov al,DTR ;clear the DTR not al ;bit in memory and al,mdmbits mov mdmbits,al mov dx,base add dx,MCNTRL call outp ;and the 8250 ldz: endf _lower_dtr ; ;Raise DTR. ; func _raise_dtr test fossil,1 jz rd1 mov ah,6 ;AH= DTR mov al,1 ;AL= raise DTR call int14 jmp short rdz rd1: or mdmbits,DTR ;set DTR, mov al,mdmbits ;set the 8250 mov dx,base add dx,MCNTRL call outp ;and the 8250 rdz: endf _raise_dtr ; ;Flush both buffers. ; func __mflush test fossil,1 jz mf1 mov ah,9 ;AH= purge out call int14 mov ah,0ah ;AH= purge in call int14 jmp short mfz mf1: pushf cli mov out_head,offset out_beg + 1 mov out_tail,offset out_beg mov in_head,offset in_beg + 1 mov in_tail,offset in_beg mov in_count,0 mov out_count,0 popf mfz: endf __mflush ; ;Return true if the output buffer is not empty. ; func __mbusy mov ax,out_count ;AX= chars left test fossil,1 jz mbz mov ah,3 ;AH= status call int14 xor ax,4000h ;flip TSRE and ax,4000h ;mask TSRE mbz: endf __mbusy ; ;Return true if CD or DSR is true. ; func __cd test fossil,1 jz cd1 mov ah,3 ;AH= status call int14 mov ah,0 ;(zap UART stat) jmp short cd2 cd1: mov dx,base add dx,MSTAT call inp ;flush deltas call inp cd2: and ax,arg0 ;mask it off endf __cd ; ;Return true if there is a receive character ;ready. ; func __mconstat test fossil,1 jz mis1 mov ah,3 ;AH= status call int14 and ax,0100h ;mask RDA jmp short mis2 mis1: call inas mis2: mov ax,0 jz misz mov ax,1 misz: endf __mconstat ; ;Output a character to the modem. ; func __mconout mov al,arg0 ;AL= char test fossil,1 jz mo1 mov ah,1 ;AH= char out call int14 jmp short moz mo1: call outa moz: endf __mconout ; ;Return true if modem output ready. ; func __mconostat test fossil,1 jz mos1 mov ah,3 ;AH= status call int14 and ax,2000h ;mask THRE jmp short mos2 mos1: call outas mos2: mov ax,0 jz mosz mov ax,1 mosz: endf __mconostat ; ;Get a character from the modem ; func __mconin test fossil,1 jz mi1 mov ah,2 ;AH= read char call int14 jmp short mi2 mi1: call ina mi2: mov ah,0 miz: endf __mconin ; ;Local routine: Return a character from the ;input buffer, wait if necessary. ; ina: call inas ;check for char jz ina ;wait if none mov in_tail,bx ;else return it dec in_count ;and count it ret ; ;Return Z clear, a character in AL, and BX ;pointing to the next location if there is a ;character ready, else Z set. ; inas: call rtsctl ;RTS control mov bx,in_tail ;char in stat, inc bx cmp bx,offset in_end jb ias1 mov bx,offset in_beg ias1: mov al,[bx] cmp bx,in_head ret ; ;Put the character in AL into the output ;buffer, wait for room if necessary. ; outa: push ax oa0: call outas ;wait for room jz oa0 mov bx,out_head pop ax mov [bx],al ;stash it, inc bx cmp bx,offset out_end jb oa1 mov bx,offset out_beg oa1: mov out_head,bx inc out_count call outas ;send it ret ; ;Return Z set if the buffer is full. ; outas: call mlint ;check CTS ;; mov dx,base ;; add dx,INTENB ;; mov al,RDAINT + TBEINT + MDMINT ;; call outp mov ax,out_head sub ax,out_tail ret page ; ;Interrupt vector table, from the least three ;bits in the IIR. Note that in FIFO mode ;(16550) 1100b means FIFO Rx timeout; since ;bit 3 is masked off it is treated like an Rx ;interrupt. ; vectbl label word ; B W offset dw mlint ;000 00 modem status dw txint ;010 01 Tx interrupt dw rxint ;100 10 Rx interrupt dw lsint ;110 11 line status ; ;Interrupt service for the 8250. Check for ;either interrupt, handle both in series here. ;NOTE: BP is the 8250 base address for RxINT ;and TxINT. ; i8250: push ax push bx push dx push bp push ds mov ds,cs:dataseg mov bp,base ;pre-set BP, ; ;Loop until there are no more interrupts ;pending. ; i0: mov dx,bp add dx,INTID ;find source call inp ;of interrupt mov bx,ax and bx,111b ;only bits 0-2 test bl,1 ;check EOI jnz i4 mov ax,offset i0 push ax ;return address jmp cs:vectbl[bx] ;call it ; ;Line Status Interrupt: Overrun, Parity, etc. ;Ignore these interrupts. Clear 'em and go. ; lsint: mov dx,bp add dx,MLSTAT ;clear errors, etc call inp badint: ret ; ;Modem status change. We just watch CTS here and ;possibly reenable Tx interrupts if it is off and ;goes true. ; ;Also called by local routines to re-enable Tx ;interrupts if they were off. (Doesn't use BP.) ; mlint: mov dx,base add dx,MSTAT ;check modem call inp ;status, ;; test al,1 ;ignore all but ;; jz ml2 ;CTS changes and al,CTS ;test for CTS mov al,RDAINT + MDMINT jz ml1 ;if CTS true, or al,TBEINT ;enable Tx ints ml1: mov dx,base ;else add dx,INTENB ;disable Tx call outp ml2: ret ; ;End of interrupt: clear it and go. ; i4: mov dx,20h mov al,20h ;non-specific EOI, call outp ;to the 8259A. pop ds pop bp pop dx pop bx pop ax iret page ; ;TBE interrupt service. If no characters ;to send, mask off the transmitter interrupt. ;Output as many characters as possible, until ;we lose TBE, CTS or no more characters. ; ;BP = 8250 base address ; txint: mov dx,bp add dx,MLSTAT ;UART status, call inp ;quit if and al,TBE ;no TBE. jz p0w4 ; ;If CTS is false just exit. ; mov dx,bp add dx,MSTAT ;check modem call inp ;status, and al,CTS ;quit if jz p0w3 ;no CTS. ; ;See if any characters are waiting for output; ;if not, turn off Tx interrupts. The next output ;call will queue some up and call TxISR. ; ;NOTE: This counts by predecrement; it will go ;negative if the count was already zero. ; sub out_count,1 ;if it was <= 0 jl p0w2 ;go disable mov bx,out_tail inc bx cmp bx,offset out_end jb p0w1 mov bx,offset out_beg p0w1: cmp bx,out_head ;If empty??! je p0w2 ;then disable. mov al,[bx] ;get a char, mov out_tail,bx ;save ptr, mov dx,bp add dx,MDATA ;send it call outp ;to the modem jmp txint ;repeat ; ;No more characters to output; turn off Tx ;interrupts. ; p0w2: mov out_count,0 ;(if < 0) p0w3: mov dx,bp add dx,INTENB ;disable Tx mov al,RDAINT + MDMINT;interrupts call outp p0w4: ret ; ;Character input (RDA) interrupt. Put the ;character into the receive buffer, or throw ;it away if full. ; ;BP = 8250 base address. ; rxint: call rtsctl ;RTS control mov dx,bp add dx,MLSTAT ;check RDA call inp and al,RDA ;exit if none jz p0rz mov dx,bp add dx,MDATA ;get the char call inp mov bx,in_head cmp in_tail,bx ;check full, je rxint ;toss it if so, mov [bx],al ;stash char, inc bx cmp bx,offset in_end ;wrap ptr, jb p0r1 mov bx,offset in_beg p0r1: mov in_head,bx inc in_count ;count it, jmp rxint ;get next p0rz: ret ; ;Rx interrupt buffer RTS control. Turn RTS on ;or off depending on the condition of the ;buffer; off if the buffer becomes full, ;on if becomes half empty. ; rtsctl: mov ax,IN_SIZE ;find room sub ax,in_count ;remaining, ; ;If 1/2 empty, raise RTS if it is not already. ; cmp ax,(IN_SIZE / 2) jb rs1 ;if 1/2 or less test mdmbits,RTS ;full raise RTS jnz rsz ;if its low. or mdmbits,RTS ;set the bit, jmp short rs2 ;go set it. ; ;If almost full, lower RTS if it is raised. ; rs1: cmp ax,4 ;if nearly full ja rsz ;lower RTS mov al,RTS test mdmbits,al ;if high now jz rsz not al ;clear RTS bit and mdmbits,al rs2: mov al,mdmbits ;set control mov dx,base ;register bits add dx,MCNTRL call outp rsz: ret ; ;Low level in and out routines. These slow ;things down to accomodate the x86. ; outp: out dx,al ret inp: in al,dx ret ; -------------------------------- ; ;Set clock: Insert our routine in series ;with the existing clock. ; func _init_tick call fossil_setup mov dx,55 ;tick in Ms mov al,8 ;int #8 test fossil,1 ;use FOSSIL jz sc1 ;if present mov ah,7 ;AH= get tick call int14 sc1: mov tickint,dx ;set INTERVAL mov intno,al ;INT NUMBER mov cs:dataseg,ds ;set for ISR mov al,intno ;vector number, call getvec ;get vector mov word ptr cs:oldvec,bx ;save it, mov word ptr cs:oldvec + 2,es mov al,intno mov dx,cs ;set for ticker mov ds,dx mov dx,offset ticker call setvec endf _init_tick ; ;Remove our timer from the circuit. ; func _uninit_tick mov al,intno mov dx,word ptr cs:oldvec mov ds,word ptr cs:oldvec + 2 call setvec endf _uninit_tick ; ; h= set_tick(&timer); ; func _set_tick mov cx,MAXTICK mov ax,0 ;AX == handle st1: mov bx,ax shl bx,1 ;word ptr cmp word ptr pile[bx],0 jne st2 ;find a zero, mov dx,arg0 mov pile[bx],dx ;store pointer jmp st3 ;return handle st2: inc ax ;next handle ... loop st1 mov ax,-1 ;no free handles st3: endf _set_tick ; ; clr_tick(h); ; func _clr_tick mov ax,-1 mov bx,arg0 cmp bx,MAXTICK jae ct1 shl bx,1 mov word ptr pile[bx],0 mov ax,0 ct1: endf _clr_tick page ; ;Interrupt service routine for ;the timer ticks. AX contains the number ;of milliseconds since the last tick. This ;must preserve all registers. ; ticker: sti ;interruptable push ds push si push cx push bx push ax mov ds,cs:dataseg ;set DS mov cx,tickint ;interval xor bx,bx ;index & count xor ax,ax ;zero ; ;For each non-zero handle, add the interval in ;Ms to each 32-bit timer. ; tf1: mov si,pile[bx] or si,si jz tf2 add word ptr [si],cx ;add Ms since adc word ptr [si + 2],ax;last tick tf2: add bx,2 cmp bx,(MAXTICK * 2) jb tf1 pop ax pop bx pop cx pop si pop ds jmp dword ptr cs:[oldvec] ;chain. ; ;Set interrupt vector AL to DS:DX. ; setvec: push es mov bx,0 mov es,bx mov bl,al mov bh,0 shl bx,1 shl bx,1 pushf cli mov es:[bx],dx mov es:[bx + 2],ds popf pop es ret ; ;Get interupt vector AL, return in ES:BX. ; getvec: push ds mov di,0 ;set DS to 0 mov ds,di ;(low mem) mov ah,0 mov di,ax ;DI= int # shl di,1 shl di,1 ;DI= DWORD ptr pushf cli mov bx,ds:[di] mov es,ds:[di + 2] popf pop ds ret end