
%if 0

Serial port handling code

2019 by C. Masloch
 based on http://www.sci.muni.cz/docs/pc/serport.txt The Serial Port rel. 14

Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.

DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.

%endif


	usesection lDEBUG_DATA_ENTRY

iisphwreset serial_interrupt_handler

  align 2, db 0
iispentry serial_interrupt_handler, 80h, serial_interrupt_handler
  push  ax
  push  cx
  push  dx  ; first save the regs we need to change
  push  ds
  push  si
  mov  al,20h  ; acknowledge interrupt
  out  20h,al

  push cs
  pop ds
  cld
ih_continue:
  mov dx, [baseport]
  inc dx
  inc dx	; (base + 2) write FCR, read IIR
%if _USE_TX_FIFO
  mov al, byte [serial_fcr_setting]
  and al, ~ 0000_0110b
  out dx, al	; (write FCR) configure FIFOs
  xor ax, ax
%else
  xor ax, ax
  out dx, al	; (write FCR) configure FIFOs
  nop
%endif
  in al, dx	; (read IIR) get interrupt cause
  test al, 1	; did the UART generate the int?
  jnz ih_sep	; no, then it's somebody else's problem
  and al, 6	; mask bits not needed
  mov si, ax	; make a pointer out of it
  dec dx
  dec dx	; = base
  call near word [serial_interrupt_table + si]  ; serve this int
  jmp  ih_continue  ; and look for more things to be done
ih_sep:
  pop  si
  pop  ds
  pop  dx  ; restore regs
  pop  cx
  pop  ax
  iret

	align 2, db 0
serial_interrupt_table:  dw  int_modem,int_tx,int_rx,int_status


int_modem:
  ; just clear modem status, we are not interested in it
  add dx, 6
  in al, dx		; read MSR
  retn


int_tx:
  mov dx, [baseport]
  mov si, word [txtail]

  push dx
  add dx, 5
  in al, dx		; (base + 5) read LSR
  pop dx
  test al, 20h		; Transmitter Holding Register Empty ?
  jz itx_setup_int	; no, it was a spurious interrupt -->
	; This conditional detects the condition specified in
	;  the section "Known problems with several chips":
	; When a 1 is written to the bit 1 (Tx int enab) in the
	;  IER, a Tx interrupt is generated. This is an erroneous
	;  interrupt if the THRE bit is not set. [So don't set
	;  this bit as long as the THRE bit isn't set. CB]

  ; check if there's something to be sent
%if _USE_TX_FIFO
	mov cx, 1
	test byte [serial_flags], sf_built_in_fifo
	jz @F
	mov cl, byte [serial_fifo_size]
@@:
%endif
itx_more:
  cmp  si, word [txhead]
  je  itx_nothing
  lodsb
  out dx, al		; write it to the THR
  ; check for wrap-around in our fifo
  tx_checkwrap
%if _USE_TX_FIFO
  ; send as much bytes as the chip can take when available
  loop itx_more
%endif
itx_setup_int:
  cmp si, word [txhead]
  je itx_nothing
  inc dx
  mov al, 0000_0011b
  out dx, al		; write to IER
  jmp itx_dontstop
itx_nothing:
  ; no more data in the fifo, so inhibit TX interrupts
  inc dx
  mov al, 0000_0001b
  out dx, al		; write to IER
itx_dontstop:
  mov word [txtail], si
  retn


int_rx:
  mov  si, word [rxhead]
irx_more:
  mov dx, [baseport]
  in al, dx		; read from RBR
  cmp al, 3
  jne @FF
  testopt [serial_flags], sf_ctrl_c
  jz @F
  setopt [serial_flags], sf_double_ctrl_c
@@:
  setopt [serial_flags], sf_ctrl_c
@@:
  mov  byte [si], al
  mov ax, si
  inc  si
  ; check for wrap-around
  rx_checkwrap
  cmp word [rxtail], si
  je @FF
  ; see if there are more bytes to be read
  add dx, 5
  in al, dx		; read LSR
  test al, 1		; Data Available ?
  jnz irx_more
.end:
  mov word [rxhead], si
;  test al, 20h	; Transmitter Holding Register Empty ?
;  jnz int_tx	; yes, do transmit next -->
	; Sometimes when sending and receiving at the
	; same time, TX ints get lost. This is a cure.
;  retn
  jmp int_tx		; (this checks for THRE)

@@:
  mov dx, [baseport]
  in al, dx		; read RBR (discard)
  db __TEST_IMM16	; (skip mov)
@@:
  mov si, ax
  add dx, 5
  in al, dx		; read LSR
  test al, 1		; Data Available ?
  jnz @BB
  jmp .end


int_status:
  ; just clear the status ("this trivial task is left as an exercise
  ; to the student")
  add dx, 5
  in al, dx		; read LSR
  retn


	usesection lDEBUG_CODE

	; OUT:	ZR if no new character in buffer
	;	NZ if new character read,
	;	 al = character
	; STT:	ds = debugger segment
serial_receive_char:
	push si
		; see if there are bytes to be read from the fifo
	mov si, word [rxtail]

	cmp si, word [rxhead]
	je .nodata
	lodsb
%if _ECHO_RX_TO_TX
 %if _RX_TO_TX_ADD_LF
	call serial_send_char_add_lf
 %else
	call serial_send_char
 %endif
%endif
		; check for wrap-around
	rx_checkwrap
	mov  word [rxtail], si
	test si, si	; (NZ)
	jmp .return

.nodata:
	xor ax, ax	; (ZR)
.return:
	pop si
	retn


serial_install_interrupt_handler:
%if _PM
	call ispm
	jnz .rm

	mov ax, 0200h
	mov bl, byte [serial_use_intnum]
	int 31h						; cx:dx = int vector
	mov word [serial_interrupt_handler.next], dx
	mov word [serial_interrupt_handler.next + 2], cx

	mov ax, 0201h
	mov bl, byte [serial_use_intnum]
	mov cx, word [pspdbg]		; cx => lDEBUG_DATA_ENTRY
	mov dx, serial_interrupt_handler
	int 31h
	retn

.rm:
%endif

	call InDos
	jz .notindos
	push es
	push ds
	xor ax, ax
	mov ds, ax
	mov al, byte [ss:serial_use_intnum]
	add ax, ax
	add ax, ax
	xchg ax, si
	les bx, [si]
	mov word [ss:serial_interrupt_handler.next], bx
	mov word [ss:serial_interrupt_handler.next+2], es
	mov word [si], serial_interrupt_handler
	mov word [si + 2], ss		; ss => lDEBUG_DATA_ENTRY
	pop ds
	pop es
	retn

.notindos:
  ; install interrupt handler first
	push es
	mov ah, 35h
	mov al, byte [serial_use_intnum]
  int  21h
  mov  word [serial_interrupt_handler.next], bx
  mov  word [serial_interrupt_handler.next+2], es
	pop es
	mov ah, 25h
	mov al, byte [serial_use_intnum]
  mov  dx, serial_interrupt_handler	; ds => lDEBUG_DATA_ENTRY
  int  21h
  retn

serial_clear_fifos:
  ; clear fifos (not those in the 16550A, but ours)
  mov  ax, rxfifo
  mov  word [rxhead], ax
  mov  word [rxtail], ax
  mov  ax, txfifo
  mov  word [txhead], ax
  mov  word [txtail], ax
  retn


	numdef SERIAL_DL_WORD, 0

serial_init_UART:
  ; initialize the UART
  mov dx, [baseport]
  add dx, 3		; (base + 3) read/write LCR
  in al, dx		; read LCR
  mov byte [serial_save_lcr], al
  mov al, 80h		; DLAB = 1
  out dx, al		; write LCR, make DL register accessible
  push dx
  mov dx, [baseport]	; (base)
%if _SERIAL_DL_WORD
  in ax, dx		; read bps rate divisor (DL)
  mov word [serial_save_dl], ax
  mov ax, word [serial_use_dl]
  out dx, ax		; write bps rate divisor (DL)
%else
  in al, dx		; read bps rate divisor low byte (DL)
  inc dx
  mov byte [serial_save_dl], al
  in al, dx		; read bps rate divisor high byte (DL)
  mov byte [serial_save_dl + 1], al
  mov al, byte [serial_use_dl + 1]
  out dx, al		; write bps rate divisor high byte (DL)
  dec dx
  mov al, byte [serial_use_dl]
  out dx, al		; write bps rate divisor low byte (DL)
%endif
  pop dx		; (base + 3) write LCR
  mov al, byte [serial_use_params]
			; DLAB = 0 and control parameters
  out dx, al		; write parameters

  ; is it a 16550A?
  dec dx		; (base + 2) write FCR, read IIR
%if _USE_TX_FIFO
  mov al, 0000_0111b
  or al, byte [serial_use_fifo]
  mov byte [serial_fcr_setting], al
  out dx, al			; (write FCR) try to clear and enable FIFOs
  nop
  in al, dx			; read IIR
  or byte [serial_flags], sf_built_in_fifo
				; in case of built-in tx FIFO
  and al, 1100_0000b		; mask of FIFO functional bits
  cmp al, 1100_0000b		; both bits set ?
  je @F				; yes -->
  and byte [serial_flags], ~ sf_built_in_fifo
				; no built-in tx FIFO
  xor ax, ax
  mov byte [serial_fcr_setting], al
  out dx, al			; (write FCR) disable the FIFOs
@@:
%else
  xor ax, ax
  out dx, al			; (write FCR) disable the FIFOs
%endif
  dec dx		; (base + 1)
  in al, dx		; read IER
  mov byte [serial_save_ier], al
  mov al, 0000_0001b	; allow RX interrupts
  out dx, al		; write to IER
  dec dx		; (base + 0) read RBR
  in al, dx		; clear receiver
  add dx, 5		; (base + 5) read LSR
  in al, dx		; clear line status
  inc dx		; (base + 6) read MSR
  in al, dx		; clear modem status
	; free interrupt in the ICU
  mov cx, word [serial_use_irqmask]
  not cx		; negated mask of bits to change
  xor bx, bx		; all bits clear (= IRQ ON)
  call set_irq
  mov word [serial_save_irq_off], bx
  mov word [serial_save_irq_mask], cx
 	; and enable ints from the UART
  dec dx
  dec dx		; (base + 4)
  in al, dx		; read MCR
  mov byte [serial_save_mcr], al
  mov al, 0000_1000b
  out dx, al		; write MCR
  retn

		; IBP:	cx = negated mask of bits to change
		;	 (if bit is clear, modify corresponding IRQ)
		;	bx = mask of what to set bits to (0 = IRQ ON, 1 = IRQ OFF)
		; OUT:	bx = mask of bits previously set
		; CHG:	ax
set_irq:
  push dx
  mov dx, 1
.loop:
  test dx, cx
  jnz .next
  test dl, dl
  jz .high

.low:
  in al, 21h		; get PIC configuration
   push ax
  not dx		; dx = mask of bits to keep
  and al, dl		; mask to 0 the bit to set
    push bx
  not dx		; dx = mask of bits to change
  and bl, dl		; get bit state to change to
  or al, bl		; set this bit state
  out 21h, al		; configure the PIC
    pop bx		; = saved states / still to set states
  not dx		; dx = mask of bits to keep
   pop ax		; = prior config
  and bl, dl		; clear bits to change
  not dx		; dx = mask of bits to change
  and al, dl		; separate out only bits to change
  or bl, al		; set in bx
  jmp .next

.high:
  in al, 0A1h		; get PIC configuration
   push ax
  not dx		; dx = mask of bits to keep
  and al, dh		; mask to 0 the bit to set
    push bx
  not dx		; dx = mask of bits to change
  and bh, dh		; get bit state to change to
  or al, bh		; set this bit state
  out 0A1h, al		; configure the PIC
    pop bx		; = saved states / still to set states
  not dx		; dx = mask of bits to keep
   pop ax		; = prior config
  and bh, dh		; clear bits to change
  not dx		; dx = mask of bits to change
  and al, dh		; separate out only bits to change
  or bh, al		; set in bx

.next:
  add dx, dx
  jnz .loop
  pop dx
  retn

serial_clean_up:
  xor ax, ax
  mov dx, [baseport]
  add dx, 4		; (base + 4)
			; disconnect the UART from the int line
  out dx, al		; write MCR
  dec dx
  dec dx
  dec dx		; (base + 1) disable UART ints
  out dx, al		; write IER
  inc dx		; (base + 2)
			; disable the FIFOs (old software relies on it)
  out dx, al		; write FCR

  ; reset the UART
  mov dx, [baseport]
  add dx, 3		; (base + 3) read/write LCR
  mov al, 80h		; DLAB = 1
  out dx, al		; write LCR, make DL register accessible
  push dx
  mov dx, [baseport]	; (base)
%if _SERIAL_DL_WORD
  mov ax, word [serial_save_dl]
  out dx, ax		; write bps rate divisor (DL)
%else
  mov al, byte [serial_save_dl]
  out dx, al		; write bps rate divisor low byte (DL)
  inc dx
  mov al, byte [serial_save_dl + 1]
  out dx, al		; write bps rate divisor high byte (DL)
%endif
  pop dx		; (base + 3) write LCR
  mov al, byte [serial_save_lcr]
  out dx, al		; write parameters

  dec dx		; (base + 2) write FCR, read IIR
  xor ax, ax
  out dx, al		; (write FCR) disable the FIFOs
  dec dx		; (base + 1)
  mov al, byte [serial_save_ier]
  out dx, al		; write to IER
  mov bx, [serial_save_irq_off]
			; bits clear for IRQ ON
  mov cx, [serial_save_irq_mask]
			; negated mask of bits to change
  call set_irq
  inc dx
  inc dx
  inc dx		; (base + 4)
  mov al, byte [serial_save_mcr]
  out dx, al		; write MCR

  ; restore int vector
%if _PM
	call ispm
	jnz .rm

	mov ax, 0201h
	mov bl, byte [serial_use_intnum]
	mov cx, [serial_interrupt_handler.next + 2]
	mov dx, [serial_interrupt_handler.next]
	int 31h
	retn

.rm:
%endif

	call InDos
	jz .notindos
	push ds
	xor ax, ax
	mov ds, ax
	mov al, byte [ss:serial_use_intnum]
	add ax, ax
	add ax, ax
	xchg ax, si
	push word [ss:serial_interrupt_handler.next+2]
	push word [ss:serial_interrupt_handler.next]
	pop word [si]
	pop word [si + 2]
	pop ds
	retn

.notindos:
	push ds
	mov ah, 25h
	mov al, byte [serial_use_intnum]
  lds  dx, [serial_interrupt_handler.next]
  int  21h
	pop ds
  retn

serial_send_char_add_lf:
	push ax
.loop:
	call serial_send_char
	cmp al, 13	; add LF after CR; change it if you don't like it
	mov al, 10
	je .loop
	pop ax
	retn

serial_send_char:
  push si
  push cx
  push dx
  push es

  mov  si, word [txhead]
  mov  byte [si],al
  inc  si
  ; check for wrap-around
  tx_checkwrap

  push ax
%if _PM
   push ax
  call push_if
%else
  pushf
%endif
  cmp word [txtail], si
  jne .no_wait

	; Because we enable the tx empty interrupt
	;  when putting data into the buffer, it
	;  should still be enabled here when the
	;  buffer is currently full. So we only
	;  need to wait for the interrupt to
	;  occur and be processed by our handler.

  xor cx, cx
  mov dx, 40h			; 0040h is a bimodal segment/selector
  mov es, dx
  mov dx, word [es:6Ch]

.wait:
  call idle
%if _PM
  call pop_if
   push ax
  call push_if
%else
  popf
  pushf
%endif

  cmp si, word [txtail]
  jne .no_wait

  cmp dx, word [es:6Ch]
  je .wait
  mov dx, word [es:6Ch]
  inc cx
  cmp cx, 5 * 18
  jb .wait

	clropt [options], enable_serial
	mov dx, msg.no_progress
	call putsz
	jmp cmd3

.no_wait:
  mov  word [txhead], si
  cli		; try to avoid interrupt while emptying buffer
  ; test if we can send a byte right away
%if 0		; int_tx checks for THRE ...-
  mov dx, [baseport]
  add dx, 5	; (base + 5)
  in al, dx	; read LSR
  test al, 20h	; Transmitter Holding Register Empty ?
  jz .crank	; no, just enable the interrupt -->
%endif

;  call int_tx	; send bytes, enables or disables the tx interrupt
	push cs
	call code_to_int_tx

%if 0		; -... and sets up the interrupt accordingly
  jmp .dontcrank
.crank:
  ; crank it up
  ; note that this might not work with some very old 8250s
  add dx, 1 - 5	; (base + 1) write IER
  mov al, 0000_0011b
  out dx, al	; enable tx empty interrupt
.dontcrank:
%endif
%if _PM
  call pop_if
%else
  popf
%endif
  pop ax
  pop es
  pop dx
  pop cx
  pop si
  retn

%if _PM
push_if:
	lframe near
	lpar word,	flags
	lpar_return
	lenter
	call ispm
	jnz .86m
.pm:
	push ax
	mov ax, 0902h
	int 31h
	test al, al	; 0 = disabled ?
	mov ax, 0	; initialise to IF=0
	jz @F
	mov ah, 2	; else, IF=1
@@:
	mov word [bp + ?flags], ax
	pop ax
	jmp .end
.86m:
	pushf
	pop word [bp + ?flags]
.end:
	lleave
	lret

pop_if:
	lframe near
	lpar word,	flags
	lenter
	call ispm
	jnz .86m
	push ax
	mov ax, 0900h	; initialise to disable VIF = 0900h
	test byte [bp + ?flags + 1], 2
	jz @F		; if to disable -->
	inc ax		; else enable VIF = 0901h
@@:
	int 31h
	pop ax
	jmp .end
.86m:
	push word [bp + ?flags]
	popf
.end:
	lleave
	lret
%endif

code_to_int_tx:
	push word [cs:.entry_retf_word]
%if _PM
			; near return address
	call ispm
	jnz .rm

	push word [cssel]
	jmp @F

%endif
.rm:
	push ss
@@:
	push word [cs:.int_tx_word]
	retf		; jump to lDEBUG_DATA_ENTRY:int_tx

	align 2, db 0
.int_tx_word:
	dw int_tx
.entry_retf_word:
	dw entry_retf


	usesection lDEBUG_DATA_ENTRY

entry_retf:
	retf
