;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;;	SERIAL HARDWARE MODULE
;;;;   (C) Copyright 1995 Gregory Ercolano                                
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; INIT THE COMRING
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
initcomring	proc near
	;;; INIT VECTORS
	mov [com1in],offset com1buf
	mov [com1out],offset com1buf
	mov [com1end],offset com1buf+COMBUF_SIZE

	mov [com2in],offset com2buf
	mov [com2out],offset com2buf
	mov [com2end],offset com2buf+COMBUF_SIZE

	;;; ZERO OUT COM1 BUFFER
	mov al,'1'
	mov cx,COMBUF_SIZE
	push ds
	pop si
	mov di,offset com1buf
	call memset		;SI:DI filled with value in AL for CX bytes

	;;; ZERO OUT COM2 BUFFER
	mov al,'2'
	mov cx,COMBUF_SIZE
	mov di,offset com2buf
	call memset		;SI:DI filled with value in AL for CX bytes

	ret
initcomring	endp


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; INIT SERIAL HARDWARE
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
initserial	proc near
	push ds
	  mov ax,0040h		; bios
	  mov ds,ax
	  mov bx,[ds:0]		; com1 port address from bios
	  mov cx,[ds:2]		; com2 port address from bios
	pop ds
	mov [com1port],bx
	mov [com2port],cx

	; DISABLE COM INTERRUPTS WHILE WE FUCK WITH HARDWARE
	cli
	in al,21h
	or al,00011000b		; DISABLE IRQ3 AND IRQ4
	out 21h,al

	; DISABLE IER FOR COM1
	mov dx,[com1port]
	add dx,1
	mov al,00000000b
	out dx,al

	; DISABLE IER FOR COM2
	mov dx,[com2port]
	add dx,1
	mov al,00000000b
	out dx,al

	; COM HARDWARE INTERRUPT VECTOR INIT
	push ds
	  mov ax,0		; address interrupt table in 0000:xxxx
	  mov ds,ax
	  mov ax,cs
	  mov [ds:002eh],ax		; COM1 HARDWARE INTERRUPT (SEG)
	  mov [ds:0032h],ax		; COM2 HARDWARE INTERRUPT (SEG)

	  mov ax,offset com2int
	  mov [ds:002ch],ax		; COM2 HARDWARE INTERRUPT (OFF) 

	  mov ax,offset com1int
	  mov [ds:0030h],ax		; COM1 HARDWARE INTERRUPT (OFF) 
	pop ds

	; COM1 HARDWARE INIT
	mov dx,[com1port]
	call serialhardinit

	; COM2 HARDWARE INIT
	mov dx,[com2port]
	call serialhardinit

	in al,21h
	and al,11100111b		; ENABLE IRQ3 AND IRQ4
	out 21h,al
	ret
initserial	endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; LOCAL: INIT SERIAL HARDWARE
;	Hardware initialized to 9600,n,8,1
; 	dx=port base address
;	DX is maintained on return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
serialhardinit	proc near
	; INITIALIZE BAUD RATE
	;	Also initializes DLAB in process.. 
	;	a good thing to do first.
	;
	sti
	mov ax, 0006h		;19200 baud
	call setbaud

	; SETUP IER (Interrupt Enable Register)
	;	Interrupt whenever a character is received
	add dx,1
	in al,dx
	or al, 00000001b	; 0001=chr rdy, 0010=THRE, 0100=LSR, 1000=MSR
	out dx,al
	sub dx,1

	; SETUP LCR (Line Control Register)
	add dx,3
	mov al,0000011b		; N,8,1 (no parity, 8 bit, 1 stop bit)
	out dx,al
	sub dx,3

	; SETUP MCR (Modem Control Register)
	add dx,4
	mov al,0001111b		; enable OUT1/2, DTR, RTS
	out dx,al
	sub dx,4

	; CLEAR IOR (Input/Output register) BY READING IT
	in al,dx

	; CLEAR IIR (Interrupt Ident Register) BY READING IT
	add dx,2
	in al,dx
	sub dx,2

	; CLEAR LSR (Line Status Register) BY READING IT
	add dx,5
	in al,dx
	sub dx,5

	; CLEAR MSR (Modem Status Register) BY READING IT
	add dx,6
	in al,dx
	sub dx,6

	sti
	ret
serialhardinit	endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; LOCAL: SET BAUD RATE
;	dx=port base for com port
;	ax=baud rate divisor
;	DX is maintained
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
setbaud	proc near
	push ax			; save baud rate divisor

	  ; ENABLE DLAB (Divisor Latch Access Bit)
	  add dx,3
	  in al,dx
	  or al,10000000b	; DLAB BIT SET
	  out dx,al
	  sub dx,3

	; SET BAUD RATE TO 9600 (what other baud rate would anyone want? ;)
	pop ax
	out dx,al		; Divisor Low
	xchg al,ah
	inc dx
	mov al,00h		; Divisor Hi
	out dx,al
	dec dx

	; DISABLE DLAB
	add dx,3
	in al,dx
	and al,01111111b	; DLAB BIT CLEAR
	out dx,al
	sub dx,3
	ret
setbaud	endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; COM1 AND COM2 SERIAL HARDWARE INTERRUPT ENTRY POINT
;	When data is received on the serial ports, these interrupts
;	are executed.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
com1int:
	cli
	push ax
	push bx
	push dx
	push si
	push ds

        mov ax,cs
        mov ds,ax
        mov dx,[com1port]	; I/O port
	mov bx,[com1in]		; buffer head
        in al,dx		; get waiting char
	mov [ds:bx],al		; save it in ring buffer

	; ADVANCE BUFFER HEAD
	RING_ADVANCE com1in,com1end,com1buf

	mov al,20h		; ACKNOWLEDGE IRQ3
	out 20h,al

; COMn END OF INTERRUPT
comeoi:
	pop ds
	pop si
	pop dx
	pop bx
	pop ax
	sti
	iret
	
com2int:
	cli
	push ax
	push bx
	push dx
	push si
	push ds

        mov ax,cs
        mov ds,ax
        mov dx,[com2port]	; I/O port
	mov bx,[com2in]		; buffer head
        in al,dx		; get waiting char
	mov [ds:bx],al		; save it in ring buffer

	; ADVANCE BUFFER HEAD
	RING_ADVANCE com2in,com2end,com2buf

	mov al,64h		; ACKNOWLEDGE IRQ4
	out 20h,al

	jmp short comeoi

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; OUTPUT BYTE IN AL TO SERIAL PORT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
serialout:
	push dx
	push ds
	  push ax
	    mov ax,0040h		; bios
	    mov ds,ax
	    mov dx,[ds:0000h]	; com IO port
	    add dx,5		; LSR
so_wait:
	    in al,dx		; WAIT FOR LAST CHARACTER TO FINISH SEND
	    and al,01100000b	; test that both THRE and TSRE bits are set
	    cmp al,60h
	    jne so_wait
	    sub dx,5		; I/O REG
	  pop ax
	  out dx,al
	pop ds
	pop dx
	ret

; READ BYTE FROM INTERRUPT DRIVEN SERIAL FIFO BUFFER
;	Returns no carry if no characters ready.
;	Otherwise, carry is set, and character received in AL
;
serialin:
	mov ax,[com1in]
	cmp ax,[com1out]
	jz si_nochar

	; READ BYTE FROM FIFO BUFFER
	mov bx,[com1out]
	mov al,[ds:bx]
	RING_ADVANCE com1out,com1end,com1buf
	mov ah,al
	inc ah
	cmp al,ah
	stc
	ret
si_nochar:
	clc
	ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SEND STRING AT [ds:si] TO SERIAL PORT
;	AX and SI trashed
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
serialoutstring	proc near
sos_loop:
	mov al,[ds:si]
	cmp al,0
	jz sos_done
	call serialout
	inc si
	jmp sos_loop
sos_done:
	ret

serialoutstring	endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SEND A BREAK SIGNAL TO THE REMOTE
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
serialbreak	proc near
	push ax
	push dx
	  mov dx,[com1port]
	  add dx,3			; LCR
	  in al,dx
	  or al,01000000b		; bit #6 set: break mode
	  out dx,al

	  ; NOW HOLD IN THIS STATE FOR ONE SECOND
	  mov cx,18			; 18 ticks of clock == 1 second
	  call wait_ticks

	  xor al,01000000b		; bit #6 off = disable break
	  out dx,al
        pop dx
        pop ax
	ret
serialbreak	endp