;      /*****************************************************************/
;      /*                                                               */
;      /*           Copyright (c) IBM Corporation  1987, 1992           */
;      /*                     All Rights Reserved                       */
;      /*                                                               */
;      /*****************************************************************/
;      /******************* START OF SPECIFICATIONS *********************/
;      /*                                                               */
;      /*  SOURCE FILE NAME: SERIAL.ASM                                 */
;      /*                                                               */
;      /*  DESCRIPTIVE NAME: Serial IO routine                          */
;      /*                                                               */
;      /*  COPYRIGHT:  IBM Corp. 1992                                   */
;      /*                                                               */
;      /*  STATUS:  Version 1.0                                         */
;      /*                                                               */
;      /*  NOTES: This module contains routines to manage a COM port.   */
;      /*                                                               */
;      /*  ENTRY POINTS:                                                */
;      /*      See public statements                                    */
;      /*  EXTERNAL REFERENCES:                                         */
;      /*      See extrn statements                                     */
;      /*                                                               */
;      /******************* END  OF  SPECIFICATIONS *********************/
.xlist
  include pensegs.inc
  include pen.inc
  include serial.inc
  include devhlp.inc
  include struc.inc
.list

;------- ABIOS ERRORs --------------------
MSG_MEMORY_ALLOCATION_FAILED    EQU     0
ERROR_LID_ALREADY_OWNED         EQU     1
ERROR_LID_DOES_NOT_EXIST        EQU     2
ERROR_ABIOS_NOT_PRESENT         EQU     3
ERROR_NOT_YOUR_LID              EQU     4
ERROR_INVALID_ENTRY_POINT       EQU     5

;------- LOCAL EQUATES -------------------
INTERRUPT_TEST         EQU 1000b
INTERRUPT_INIT_MASK    EQU 07h

.286p

;------------------------------------------------------------------------------
;declare external variables
;------------------------------------------------------------------------------

extrn Device_Help    : dword
extrn Dev_ErrorCount : dword

;------------------------------------------------------------------------------
;declare external routines
;------------------------------------------------------------------------------

extrn Trc_INT : near
extrn Trc_DD  : near

CSEG     SEGMENT
         ASSUME    CS:CGROUP, SS:nothing, ES:nothing, DS:DGROUP

;---- SERVICE ROUTINES --------------------------------------------------------
;
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
; Change the baud rate
; bx = dcb
; si = serial struc
; al = baud rate
;------------------------------------------------------------------------------
public Ser_ChangeBaud
Ser_ChangeBaud proc near

  ; disable interrupts for the device at the PIC

  push ax
  call Ser_DisableInts
  pop  ax

  mov  [si].comdivisor, al

; Reset DLAB in order to set baud rate

  mov  ah, LCR
  mov  al, 80H
  call Ser_DoOut

; Set  Baud

  sub  al, al
  mov  ah, LATMSB
  call Ser_DoOut
  mov  al, [si].comdivisor
  mov  ah, LATLSB
  call Ser_DoOut

; Reset DLAB, set LCR

  mov  ah, LCR
  mov  al, [si].comLCR
  call Ser_DoOut

; Enable device at the PIC

  call Ser_EnableInts

  ret
Ser_ChangeBaud endp

;------------------------------------------------------------------------------
; set state of FIFO
;  bx = dcb
;  si = serial struc
;------------------------------------------------------------------------------
public Ser_FIFO
Ser_FIFO          Proc  Near
 .if <[si].fcomFIFO ne 0>
    mov  ah, FCR
    mov  al, [si].comFCR
    call Ser_DoOut
 .endif
 ret
Ser_FIFO endp

;------------------------------------------------------------------------------
; resume interrupt driven events
;  bx = dcb
;  si = serial struc
;------------------------------------------------------------------------------
public Ser_EnableInts
Ser_EnableInts    Proc  Near
  pushf                           ; save state of IF
  DISABLE                         ; Hold interrupts for 8259 Update

; begin defect 69804

; if interrupt level is < 8 process the master PIC
  test [si].interruptLevel, INTERRUPT_TEST
  jz   enable_master

; process slave PIC
  in   al, 0A1h
  and  al, [si].enable_8259
  out  0A1h, al
  popf
  ret

; process master PIC
enable_master:

; end defect 69804

  in   al, 21h                    ; Get current Interrupt Mask
  and  al, [si].enable_8259       ; Set serial Interrupt IRQ Bit - OFF
  out  21h, al                    ; set innterrupt mask
  popf                            ; restore interrupt flag state
  ret
Ser_EnableInts    EndP

;------------------------------------------------------------------------------
; suspend interrupt driven events
;  bx = dcb
;  si = serial struc
;------------------------------------------------------------------------------
public Ser_DisableInts
Ser_DisableInts    Proc  Near
  pushf                              ; save state of IF
  DISABLE                         ; Hold interrupts for 8259 Update

; begin defect 69804

; if interrupt level is < 8 process the master PIC
  test [si].interruptLevel, INTERRUPT_TEST
  jz   disable_master

; process slave PIC
  in   al, 0A1h
  or   al, [si].disable_8259
  out  0A1h, al
  popf
  ret

; process master PIC
disable_master:

; end defect 69804

  in   al, 21h                    ; Get current Interrupt Mask
  or   al, [si].disable_8259      ; Set Mouse Interrupt IRQ Bit - ON
  out  21h, al                    ; Reset 8259 holding Mouse ints
  popf                            ; restore interrupt flag
  ret
Ser_DisableInts    EndP

;------------------------------------------------------------------------------
; output a byte
;  ah = register offset from base
;  al = byte to write
;  si = serial struc
;------------------------------------------------------------------------------
public  Ser_DoOut
Ser_DoOut   proc
  mov  dx, [si].port
  add  dl, ah
  out  dx, al
  ;MyIODelay
  ret
Ser_DoOut   endp

;------------------------------------------------------------------------------
; write a byte, check that port is ready for it
;  al = byte to write
;  si = serial struc
;------------------------------------------------------------------------------
public  Ser_DoPut
Ser_DoPut   proc
  push  ax
  .repeat
    mov   ah,LSR
    call  Ser_DoIn
    test  al, 020h
  .until  nz
  pop   ax
  mov   ah,TXB
  call  Ser_DoOut
  ret
Ser_DoPut   endp

;------------------------------------------------------------------------------
; get a byte
;  ah = register offset from base
;  si = serial struc
; returns
;  al = byte from port
;------------------------------------------------------------------------------
public  Ser_DoIn
Ser_DoIn    proc
  mov  dx, [si].port
  add  dl, ah
  in   al, dx
  ;MyIODelay
  ret
Ser_DoIn    endp


;---- PROCESS BYTES -----------------------------------------------------------
;
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
; interrupt handler
;  bx = dcb
;  si = serial struc
;------------------------------------------------------------------------------
public Ser_InterruptHandler
Ser_InterruptHandler proc far
  DISABLE

  TraceInt

; issue the EOI and disable the IRQ

  call Ser_DisableInts

; mov  al, [si].interruptLevel
; mov  dl, DevHlp_EOI
; call Device_Help

  ENABLE

; process bytes

  .repeat

     call readByte                   ; returns al = interrupt data

     push si
     call [si].@processByte          ; bx = dcb
     pop  si                         ; al = byte

     mov  dx, [si].port               ; Setup Mouse Input Port Number
     add  dx, LSR                     ; Check Line Status Reg
     in   al, dx                      ; Read Line Status Data

  .until <bit AL z 01H> near           ; check for more data

  call Ser_EnableInts                ; make sure IRQ enabled

  mov  al, [si].interruptLevel
  mov  dl, DevHlp_EOI
  call Device_Help

  clc                            ; clear carry for our int

  ret                            ; return to intr hndlr
Ser_InterruptHandler endp

;------------------------------------------------------------------------------
; read a byte for the serial port
;  si = serial struc
;------------------------------------------------------------------------------
Public   readByte
readByte Proc  Near

  DISABLE

  MOV  DX, [si].port               ; Setup Mouse Input Port Number
  IN   AL, DX                      ; Read incoming Interrupt data Byte
  PUSH AX                          ; Save Data Byte
  ADD  DX, LSR                     ; Check Line Status Reg
  IN   AL, DX                      ; Read Line Status Data
  MOV  cl, AL                      ; Get Error Code in cl
  POP AX                           ; Restore Data Byte

  .IF <bit cl nz LS_PERR+LS_FERR>                 ; If an Error occured then
     .386p
     inc Dev_ErrorCount
     .286p
     push bx
     call [si].@errorRtn           ; bx = dcb
     call Ser_Error
     pop  bx
     xor  ax,ax                    ; this won't match sync bits
  .ENDIF                           ; Data Error Tests

 ENABLE

  ret
readByte EndP

;---- INITIALIZATION ROUTINES -------------------------------------------------
; note: Must be in resident code incase of IO error
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
; Initialize the port.
;  si = serial data
;------------------------------------------------------------------------------
public Ser_Init
Ser_Init Proc

; claim ASYNC port to keep COM.SYS's hands off

  mov  al, 06h                   ; Async Device ID
  mov  bl, [si].comPort          ; Get the LID
  xor  dh, dh
  mov  dl, DevHlp_GetLIDEntry
  call Device_Help
  .if <c>
     .if <ax ne ERROR_ABIOS_NOT_PRESENT>
        PANIC PANIC_SERNOLID
     .endif
  .else
     or   [si].serialStatus,ABIOSLID
  .endif

; Zero out BIOS data area for the Communications Line Port Base address.
;                     40:0 for Com Port 1
;                     40:2 for Com Port 2
;                     40:4 for Com Port 3
;                     40:6 for Com Port 4
  push es
  mov  bl, [si].comPort
  dec  bl
  xor  bh, bh
  shl  bx, 1
  push 40h
  pop  es
  mov  word ptr es:[bx], 0
  pop  es


; Build 8259 IC Mask from returned IRQ #

  mov  cl, [si].interruptLevel

; begin defect 69804

; map interrupt levels 8-15 to 0-7 for assigning disable_8259 and enable_8259

  and  cl, INTERRUPT_INIT_MASK

; end defect 69804

  mov  ax, 0001h
  shl  ax, cl
  mov  [si].disable_8259, al       ; Save Disable 8259 Mask
  not  al                          ; Invert Mask for Enable Bits
  mov  [si].enable_8259, al        ; Save Enable 8259 Mask

; check FIFO level
  mov  ah, FCR
  mov  al, FIFO_ENABLE
  call Ser_DoOut
  mov  ah, IIR
  call Ser_DoIn

  .if < bit al nz FIFOMASK >
     mov  [si].fcomFIFO, FIFO_ENABLE
  .else
     mov  [si].fcomFIFO, FIFO_DISABLE
  .endif

; Disable FIFO
  mov  ah, FCR
  mov  al, 0
  call Ser_DoOut

; Check to see if port exists.

  mov  ah, IIR
  call Ser_DoIn
  test al, IIRMASK            ; Check reg permanent bits
  .if <nz>                    ; if port does not exist
     PANIC PANIC_SERNOPORT
  .else
     or   [si].serialStatus,COMEXIST
  .endif

; Now go claim the IRQ level.

  push bx
  mov  ax, [si].@IntHandler           ; offset of interrupt routine
  xor  bh, bh                         ; clear upper byte
  mov  bl, [si].interruptLevel                      ; get IRQ needed
  xor  dh, dh                         ; IRQ level as unshared
  mov  dl, DevHlp_SetIRQ              ; function number
  call Device_Help                    ; go do it
  pop  bx                             ; restore dcb

  .if <c>                             ; if error
     PANIC PANIC_SERNOIRQ             ; (returns with cary set)
  .else
     or  [si].serialStatus,SETIRQ_DONE
  .endif

public Ser_Error
Ser_Error:

; Disable device from interrupting at the PIC

  call Ser_DisableInts

; Disable interrupts at the device

  mov  ah, IER
  xor  al, al
  call Ser_DoOut

; Reset DLAB in order to set baud rate

  mov  ah, LCR
  mov  al, 80H
  call Ser_DoOut

; Set  Baud

  sub  al, al
  mov  ah, LATMSB
  call Ser_DoOut
  mov  al, [si].comdivisor
  mov  ah, LATLSB
  call Ser_DoOut

; Reset DLAB, set LCR

  mov  ah, LCR
  mov  al, [si].comLCR
  call Ser_DoOut

; Set Modem Control Reg

  mov  ah, MCR
  mov  al, [si].comMCR
  call Ser_DoOut

; Read stray data

  mov  ah, RXB
  mov  cx, 0FFFFH
  .repeat
    call Ser_DoIn
  .loop

; call back to device dependent routine to do its intialization

  mov  bx,[si].@DCB
  CALL_NZ_OK [si].@InitRtn

; set FIFO

  call Ser_FIFO

; Enable interrupts at device

  mov  ah, IER
  mov  al, IERMASK
  call Ser_DoOut

; Enable device at the PIC

  call Ser_EnableInts

  ret
Ser_Init    endp

CSEG ends


end
