home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS - Coast to Coast / simteldosarchivecoasttocoast.iso / pcmag / vol8n19.zip / COURIERS.ASM < prev    next >
Assembly Source File  |  1989-10-03  |  20KB  |  806 lines

  1.     PAGE    60,132
  2.     TITLE    'Couriers:  Serial-port Services for 1stClass'
  3.  
  4. TIMER    =    46CH        ; low-memory timer word
  5. CTRL_Q    =    11H        ; ASCII Control-Q/XON/DC1
  6. CTRL_S    =    13H        ; ASCII Control-S/XOFF/DC3
  7.  
  8. ; Service options
  9.  
  10. INPUT_FLOW    =    01H    ; handle input flow control (send ^S/^Q)
  11. OUTPUT_FLOW    =    02H    ; handle output flow control (receive ^S/^Q)
  12.  
  13. ; COM port register offsets
  14.  
  15. COM_TXB=    0    ; Transmit Buffer
  16. COM_RXB=    0    ; Receive Buffer
  17. COM_DLL=    0    ; Divisor Latch LSB
  18. COM_DLM=    1    ; Divisor Latch MSB
  19. COM_IER=    1    ; Interrupt Enable Register
  20. COM_IIR=    2    ; Interrupt Identification Register
  21. COM_LCR=    3    ; Line-Control Register
  22. COM_MCR=    4    ; Modem Control Register
  23. COM_LSR=    5    ; Line Status Register
  24. COM_MSR=    6    ; Modem Status Register
  25.  
  26. PSEG    SEGMENT
  27.     ASSUME    CS:PSEG
  28.     ORG    100H
  29. ENTRY:    JMP    MAIN
  30.  
  31. PROGRAM_NAME    LABEL    BYTE
  32. INITMSG    DB    'Couriers 1.0 - (c) 1989 Ziff Communications Co.',13,10
  33.     DB    'PC Magazine * Pete Maclean$'
  34.  
  35. ; Structure for holding the fixed and operating parameters for a COM port
  36.  
  37. COM_PORT    STRUC
  38. COM_IH        DW    ?    ; (offset) address of interrupt handler
  39. COM_ADDRESS    DW    ?    ; i/o address
  40. COM_IVA        DW    ?    ; interrupt vector address
  41. COM_BIT        DB    ?    ; IRQ mask bit
  42.  
  43. COM_OPTIONS    DW    ?    ; options set for port at configuration
  44.  
  45. COM_FLAGS    DB    ?    ; various bit flags
  46. COM_OUTFLOW    DB    ?    ; flow-control character awaiting transmission
  47.  
  48. COM_IN_CTR    DW    ?    ; counter for bytes in input buffer
  49. COM_IN_LO    DW    ?    ; low threshold for input flow control
  50. COM_IN_HI    DW    ?    ; high threshold for input flow control
  51.  
  52. COM_IN_PTR    DW    ?    ; input pointer (offset)
  53. COM_IN_SEG    DW    ?    ; input segment
  54. COM_RD_PTR    DW    ?    ; read pointer (offset)
  55. COM_IN_FIRST    DW    ?    ; base offset of input buffer
  56. COM_IN_LIMIT    DW    ?    ; limit offset of input buffer
  57.  
  58. COM_OUT_PTR    DW    ?    ; output pointer (offset)
  59. COM_OUT_SEG    DW    ?    ; output segment
  60. COM_OUT_LIMIT    DW    ?    ; output limit (offset)
  61.  
  62. COM_PORT    ENDS
  63.  
  64. COM_SIZE    =    SIZE COM_PORT
  65.  
  66. ; Definitions of bit flags for the COM_FLAGS field
  67.  
  68. CF_ISF        =    01H    ; input suspended by flow control
  69. CF_OSF        =    02H    ; output suspended by flow control
  70.  
  71. ; Tables
  72.  
  73. PORT1        COM_PORT    <IH1, 3F8H, 30H, 10H>    ; COM1
  74. PORT2        COM_PORT    <IH2, 2F8H, 2CH, 08H>    ; COM2
  75. PORT3        COM_PORT    <IH3, 3E8H, 30H, 10H>    ; COM3
  76. PORT4        COM_PORT    <IH4, 2E8H, 2CH, 08H>    ; COM4
  77.  
  78. F_SWITCH    LABEL    WORD
  79.                     ; 80h - check if Couriers is loaded
  80.         DW    F_BUSY        ; 81h - check if port busy
  81.         DW    F_CONFIGURE    ; 82h - set port parameters
  82.         DW    F_INPUT        ; 83h - start input on port
  83.         DW    F_READ        ; 84h - get next input
  84.         DW    F_CLEAR_INPUT    ; 85h - flush pending input
  85.         DW    F_OUTPUT    ; 86h - initiate tranmission
  86.         DW    F_CHECK_OUTPUT    ; 87h - report output status
  87.         DW    F_ABORT_OUTPUT    ; 88h - abort current output
  88.         DW    F_OUTCH        ; 89h - output a single character
  89.         DW    F_BREAK        ; 8Ah - send BREAK
  90.         DW    F_STATUS    ; 8Bh - get port status
  91.         DW    F_SPEED        ; 8Ch - change bps rate
  92.         DW    F_DECONFIG    ; 8Dh - deconfigure port
  93.  
  94. INTSWITCH    DW    MODEM_STATUS_INT
  95.         DW    TX_INT
  96.         DW    RX_INT
  97.         DW    LINE_STATUS_INT
  98.  
  99. ;******************************************************************************
  100. ;
  101. ; COM Interrupt Handlers
  102.  
  103. IH1    PROC    NEAR            ; COM1
  104.     PUSH    BX
  105.     MOV    BX,OFFSET PORT1
  106.     JMP    SHORT INTERRUPT_HANDLER
  107. IH1    ENDP
  108.  
  109. IH2    PROC    NEAR            ; COM2
  110.     PUSH    BX
  111.     MOV    BX,OFFSET PORT2
  112.     JMP    SHORT INTERRUPT_HANDLER
  113. IH2    ENDP
  114.  
  115. IH3    PROC    NEAR            ; COM3
  116.     PUSH    BX
  117.     MOV    BX,OFFSET PORT3
  118.     JMP    SHORT INTERRUPT_HANDLER
  119. IH3    ENDP
  120.  
  121. IH4    PROC    NEAR            ; COM4
  122.     PUSH    BX
  123.     MOV    BX,OFFSET PORT4
  124. IH4    ENDP
  125.  
  126. INTERRUPT_HANDLER    PROC    NEAR    ; BX -> port table
  127.     PUSH    AX
  128.     PUSH    DX
  129.     PUSH    SI
  130.     PUSH    DS
  131.     PUSH    ES            ; CX, DI and BP are not used here
  132.     PUSH    CS            ; set DS to local segment
  133.     POP    DS
  134.     ASSUME    DS:PSEG
  135.  
  136.     MOV    DX,[BX+COM_ADDRESS]    ; DX = COM port i/o address
  137.     ADD    DL,COM_IIR        ; DX -> Interrupt ID Register
  138.     IN    AL,DX            ; identify the cause of the interrupt
  139.  
  140. INT1:    PUSH    DX            ; save IIR address
  141.     AND    AX,0006H        ; mask off interrupts type
  142.     MOV    SI,AX            ; and branch accordingly
  143.     CALL    INTSWITCH[SI]
  144.  
  145.     STI                ; allow higher priority interrupts
  146.     POP    DX            ; DX = IIR address
  147.     ADD    DL,COM_MCR-COM_IIR    ; DX -> Modem Control Register
  148.     MOV    AL,03H            ; waggle OUT2
  149.     OUT    DX,AL
  150.     MOV    AL,0BH
  151.     JMP    SHORT $+2        ; take enough time between outs
  152.     OUT    DX,AL
  153.     CLI                ; interrupts off again
  154.  
  155.     ADD    DL,COM_IIR-COM_MCR    ; DX -> Interrupt ID Register
  156.     IN    AL,DX            ; check if another interrupt is pending
  157.     TEST    AL,01H
  158.     JZ    INT1            ; loop if another one is ready
  159.  
  160.     MOV    AL,20H            ; take care of interrupt controller
  161.     OUT    20H,AL
  162.     POP    ES
  163.     POP    DS
  164.     POP    SI
  165.     POP    DX
  166.     POP    AX
  167.     POP    BX
  168.     IRET
  169. INTERRUPT_HANDLER    ENDP
  170.         
  171. ; Received Character Interrupt
  172.  
  173. RX_INT    PROC    NEAR    ; BX -> port table, DX -> COM port Interrupt Id Reg
  174.     ADD    DL,COM_LSR-COM_IIR    ; DX -> Line Status Register
  175.     IN    AL,DX            ; read COM port status
  176.     MOV    AH,AL            ; status to AH
  177.     ADD    DL,COM_RXB-COM_LSR    ; DX -> Receive Buffer
  178.     IN    AL,DX            ; read character from COM port
  179.     TEST    [BX+COM_OPTIONS],OUTPUT_FLOW    ; output flow control on?
  180.     JZ    RX2            ; if not...
  181.     CMP    AL,CTRL_S        ; is character a Control-S?
  182.     JNE    RX1            ; no, check for Control-Q
  183.     OR    [BX+COM_FLAGS],CF_OSF    ; set flag to suspend output
  184.     RET
  185.  
  186. RX1:    CMP    AL,CTRL_Q        ; is character a Control-Q?
  187.     JNE    RX2            ; if not then it is data
  188.     TEST    [BX+COM_FLAGS],CF_OSF    ; is output stopped?
  189.     JZ    RX4            ; if it is not then we need do nothing
  190.     AND    [BX+COM_FLAGS],NOT CF_OSF    ; clear suspend-output flag
  191.     ADD    DL,COM_LSR-COM_RXB    ; DX -> Line Status Register
  192.     IN    AL,DX            ; read the port status
  193.     TEST    AL,20H            ; is the transmitter buffer empty?
  194.     JZ    RX4            ; if not then output will continue
  195.     JMP    START_OUTPUT        ; else it must be restarted
  196.  
  197. RX2:    LES    SI,DWORD PTR [BX+COM_IN_PTR]    ; ES:SI = input pointer
  198.     MOV    ES:[SI],AX        ; deposit input in buffer
  199.     INC    SI            ; bump pointer to next word
  200.     INC    SI
  201.     INC    [BX+COM_IN_CTR]        ; increment counter
  202.     CMP    SI,[BX+COM_IN_LIMIT]    ; time to wrap?
  203.     JE    RX5            ; yes...
  204.  
  205. RX3:    MOV    [BX+COM_IN_PTR],SI    ; update in pointer
  206.     CMP    SI,[BX+COM_RD_PTR]    ; check for overflow
  207.     JE    RX6            ; reset the buffer with error flag
  208.  
  209.     TEST    [BX+COM_OPTIONS],INPUT_FLOW    ; input flow control on?
  210.     JZ    RX4            ; if not we are done
  211.     TEST    SI,000EH        ; else check buffer every 8th char
  212.     JNZ    RX4
  213.     TEST    [BX+COM_FLAGS],CF_ISF    ; is input staunched?
  214.     JNZ    RX4            ; if so all is well
  215.     MOV    AX,[BX+COM_IN_CTR]    ; AX = input counter
  216.     CMP    AX,[BX+COM_IN_HI]
  217.     JB    RX4
  218.     OR    [BX+COM_FLAGS],CF_ISF    ; flag input staunched
  219.     ADD    DL,COM_LSR-COM_RXB    ; DX -> Line Status Register
  220.     IN    AL,DX
  221.     TEST    AL,20H            ; transmit buffer empty?
  222.     MOV    AL,CTRL_S
  223.     JZ    RX35            ; no
  224.     ADD    DL,COM_TXB-COM_LSR    ; DX -> Transmit Buffer
  225.     OUT    DX,AL            ; transmit the ^S or ^Q
  226.     RET
  227.  
  228. RX35:    MOV    [BX+COM_OUTFLOW],AL
  229.  
  230. RX4:    RET
  231.  
  232. RX5:    MOV    SI,[BX+COM_IN_FIRST]
  233.     JMP    SHORT    RX3
  234.  
  235. RX6:    XOR    AX,AX
  236.     MOV    [BX+COM_IN_CTR],AX    ; zero the input counter
  237.     DEC    AX            ; store -1 as overflow marker
  238.     JMP    SHORT RX2
  239. RX_INT    ENDP
  240.         
  241. ; Transmit Character Interrupt
  242.  
  243. TX_INT    PROC    NEAR    ; BX -> port table, DX -> COM port Interrupt Id Reg
  244.     ADD    DL,COM_TXB-COM_IIR    ; DX -> COM port transmit buffer
  245.     XOR    AX,AX
  246.     XCHG    AL,[BX+COM_OUTFLOW]    ; is there a flow-control char waiting?
  247.     TEST    AL,AL
  248.     JNZ    TX2            ; if so go send it
  249.  
  250.     TEST    [BX+COM_FLAGS],CF_OSF    ; is output suspended by flow control?
  251.     JNZ    TX4            ; if so then we do nothing
  252.     PUSH    ES
  253.     LES    SI,DWORD PTR [BX+COM_OUT_PTR]    ; get ES:SI = output pointer
  254.     TEST    SI,SI            ; is output active?
  255.     JZ    TX3            ; no, return
  256.  
  257.     MOV    AL,ES:[SI]        ; AL = next character
  258.     INC    SI            ; bump the pointer
  259.     CMP    SI,[BX+COM_OUT_LIMIT]    ; time to stop?
  260.     JE    TX5            ; yes...
  261.  
  262. TX1:    MOV    [BX+COM_OUT_PTR],SI    ; update pointer
  263.     POP    ES
  264.  
  265. TX2:    OUT    DX,AL            ; transmit the character
  266.     CLC                ; signal character sent
  267.     RET
  268.  
  269. TX3:    POP    ES
  270.  
  271. TX4:    STC                ; signal no character sent
  272.     RET
  273.  
  274. TX5:    XOR    SI,SI
  275.     JMP    SHORT    TX1
  276. TX_INT    ENDP
  277.  
  278. ; Status Interrupts
  279.  
  280. MODEM_STATUS_INT    PROC    NEAR
  281.     ADD    DL,COM_MSR-COM_IIR
  282.     IN    AL,DX
  283.     RET
  284. MODEM_STATUS_INT    ENDP
  285.  
  286. LINE_STATUS_INT    PROC    NEAR
  287.     ADD    DL,COM_LSR-COM_IIR
  288.     IN    AL,DX
  289.     RET
  290. LINE_STATUS_INT    ENDP
  291.  
  292. ; Intercept for BIOS interrupt 14
  293.  
  294. INTERCEPT14    PROC    NEAR
  295.     PUSHF
  296.     OR    AH,AH            ; BIOS function or ours?
  297.     JNS    OUT14            ; if not ours
  298.     CMP    AH,8EH
  299.     JBE    HIT            ; it's ours
  300.  
  301. OUT14:    POPF
  302.     DB    0EAH            ; JMP FAR immediate opcode
  303. EXINT14        DD    0        ; ex-setting of interrupt 14
  304.  
  305. HIT:    POPF                ; discard flags
  306.     STI                ; allow interrupts
  307.     PUSH    BX            ; save registers
  308.     PUSH    CX
  309.     PUSH    DX
  310.     PUSH    BP
  311.     PUSH    SI
  312.     PUSH    DI
  313.     PUSH    DS
  314.     PUSH    ES
  315.     PUSH    DS            ; set ES to caller's segment
  316.     POP    ES
  317.     PUSH    CS            ; set DS to this segment
  318.     POP    DS
  319.     CLD
  320.     CALL    PROCESS            ; do a function
  321.     POP    ES
  322.     POP    DS
  323.     POP    DI
  324.     POP    SI
  325.     POP    BP
  326.     POP    DX
  327.     POP    CX
  328.     POP    BX
  329.     RETF    2
  330. INTERCEPT14    ENDP
  331.  
  332. ; Process a Couriers function
  333.  
  334. PROCESS    PROC    NEAR
  335.     SUB    AH,129            ; normalize the function code
  336.     JGE    PRO1            ; if function 81H or greater
  337.     MOV    AH,232            ; function 80H:  check that Couriers is loaded
  338.     RET                ; signal that Couriers is alive
  339.  
  340. PRO1:    DEC    AL            ; convert port number from 1-4 to 0-3
  341.     CMP    AL,4            ; validate port number
  342.     JB    PRO2            ; if it's acceptable
  343.     MOV    AH,-1            ; else error return
  344.     RET
  345.  
  346. PRO2:    PUSH    AX            ; save function code
  347.     MOV    DI,BX            ; move any BX arg to DI
  348.     MOV    BX,OFFSET PORT1        ; point BX to COM_PORT table
  349.     MOV    AH,COM_SIZE        ; calcalute offset to one we need
  350.     MUL    AH
  351.     ADD    BX,AX            ; BX -> appropriate COM table
  352.     MOV    DX,[BX+COM_ADDRESS]    ; DX = i/o address for port
  353.     POP    AX            ; AH = function code
  354.     XOR    AL,AL
  355.     XCHG    AH,AL            ; AX = function code
  356.     MOV    SI,OFFSET F_SWITCH    ; switch to appropriate procedure
  357.     ADD    SI,AX
  358.     ADD    SI,AX
  359.     JMP    [SI]    ; with BX -> COM_PORT, DX = i/o address
  360. PROCESS    ENDP
  361.  
  362. ; Check if a port is in use
  363.  
  364. F_BUSY    PROC    NEAR    ; BX -> port table, DX = port i/o address
  365.     INC    DX            ; DX -> Interrupt Enable Register
  366.     IN    AL,DX
  367.     MOV    AH,2
  368.     CMP    AL,0EDH            ; normal input for non-existent port
  369.     JE    BUSY1
  370.     DEC    AH            ; AH = 1
  371.     MOV    AH,[BX+COM_BIT]        ; AH = IRQ bit
  372.     IN    AL,21H            ; get interrupt mask
  373.     AND    AL,AH
  374.     JZ    BUSY1            ; if bit not set interrupt is enabled
  375.     DEC    AH
  376.  
  377. BUSY1:    RET    ; report in AH: 0 if port available, 1 if busy, 2 if dead
  378. F_BUSY    ENDP
  379.  
  380. ; Configure a port
  381.  
  382. F_CONFIGURE PROC NEAR    ; BX -> port table, DX=i/o address, DI=speed, CX=options
  383.     MOV    [BX+COM_OPTIONS],CX    ; save options
  384.  
  385.     ADD    DL,COM_LCR        ; DX -> Line-Control Register
  386.     XOR    AX,AX
  387.     OUT    DX,AL            ; allow access to IER
  388.     MOV    [BX+COM_FLAGS],AL    ; clear all flags for the port
  389.     MOV    [BX+COM_OUTFLOW],AL    ; and outbound flow control field
  390.  
  391.     ADD    DL,COM_IER-COM_LCR    ; DX -> Interrupt Enable Register
  392.     OUT    DX,AL            ; turn off all interrupts
  393.  
  394.     ADD    DL,COM_MCR-COM_IER    ; DX -> Modem-Control Register
  395.     MOV    AL,0BH            ; set DTR, RTS and out2 to get ints
  396.     OUT    DX,AL
  397.  
  398.     ADD    DL,COM_TXB-COM_MCR    ; DX -> base i/o address
  399.     MOV    AX,DI            ; AX = speed
  400.     CALL    SET_SPEED
  401.  
  402.     XOR    AX,AX            ; set interrupt vector
  403.     MOV    ES,AX
  404.     MOV    AX,CS
  405.     MOV    DI,[BX+COM_IVA]        ; ES:DI -> interrupt vector
  406.     CLI                ; turn interrupts off
  407.     MOV    ES:[DI+2],AX        ; set new value in vector
  408.     MOV    AX,[BX+COM_IH]
  409.     MOV    ES:[DI],AX
  410.     STI
  411.  
  412.     MOV    AH,[BX+COM_BIT]        ; mask on interrupt at controller
  413.     NOT    AH
  414.     CLI
  415.     IN    AL,21H
  416.     AND    AL,AH
  417.     OUT    21H,AL
  418.     STI
  419.  
  420.     MOV    AX,CX            ; AX = line settings
  421.     ADD    DL,COM_LCR-COM_TXB    ; DX -> Line-Control Register
  422.     MOV    AL,03H            ; select 8-bit characters
  423.     OUT    DX,AL
  424.     RET
  425. F_CONFIGURE ENDP
  426.  
  427. ; Start input
  428.  
  429. F_INPUT    PROC NEAR    ; BX -> port table, DX = i/o address, ES:DI -> buffer,
  430.             ; CX = buffer size in bytes
  431.     MOV    AX,ES            ; set up input pointers
  432.     MOV    [BX+COM_IN_SEG],AX
  433.     MOV    [BX+COM_IN_FIRST],DI
  434.     MOV    [BX+COM_RD_PTR],DI
  435.     AND    CX,0FFFEH        ; need even length
  436.     ADD    DI,CX
  437.     MOV    [BX+COM_IN_LIMIT],DI
  438.     CALL    F_CLEAR_INPUT        ; initialize the buffer to receive data
  439.     SHR    CX,1            ; convert byte count to word count
  440.     MOV    AX,CX            ; calculate thresholds for flow control
  441.     SHR    AX,1
  442.     MOV    [BX+COM_IN_LO],AX    ; send Ctrl-Q when half empty
  443.     SHR    AX,1
  444.     SUB    CX,AX
  445.     MOV    [BX+COM_IN_HI],CX    ; send Ctrl-S when three-quarters full
  446.  
  447.     IN    AL,DX            ; flush input from port
  448.     IN    AL,DX
  449.     INC    DX            ; DX -> COM_IER
  450.     IN    AL,DX            ; read Interrupt Enable Register
  451.     OR    AL,01H            ; enable input-available interrupt
  452.     JMP    $+2            ; (this may be more than is needed)
  453.     OUT    DX,AL
  454.     RET
  455. F_INPUT    ENDP
  456.  
  457. ; Read a received character
  458.  
  459. F_READ    PROC    NEAR    ; BX -> port table, DX = i/o address
  460.     MOV    SI,[BX+COM_RD_PTR]
  461.     CMP    SI,[BX+COM_IN_PTR]    ; any input?
  462.     JE    RD4            ; no...
  463.  
  464.     CLI                ; interrupts off
  465.     DEC    [BX+COM_IN_CTR]        ; decrement input counter
  466.     TEST    [BX+COM_FLAGS],CF_ISF    ; input flow-controlled off?
  467.     JZ    RD2            ; if not...
  468.     MOV    AX,[BX+COM_IN_CTR]    ; else see it it's time to resume
  469.     CMP    AX,[BX+COM_IN_LO]
  470.     JA    RD2            ; not yet
  471.     CALL    RESTART_INPUT
  472.  
  473. RD2:    STI                ; interrupts back on
  474.     MOV    AX,[BX+COM_IN_SEG]    ; get buffer segment
  475.     PUSH    DS
  476.     MOV    DS,AX
  477.     LODSW                ; load character and status
  478.     POP    DS
  479.     CMP    SI,[BX+COM_IN_LIMIT]
  480.     JNE    RD3
  481.     MOV    SI,[BX+COM_IN_FIRST]
  482.  
  483. RD3:    MOV    [BX+COM_RD_PTR],SI
  484.     OR    BX,BX            ; reset ZF
  485.  
  486. RD4:    RET
  487. F_READ    ENDP    ; returns ZF = 1 if no input available
  488.         ;    else ZF = 0 and AX = input
  489.  
  490. ; Flush pending input
  491.  
  492. F_CLEAR_INPUT PROC NEAR        ; BX -> port table, DX = i/o address
  493.     CLI                ; interrupts off while messing with pointers
  494.     MOV    SI,[BX+COM_RD_PTR]    ; make IN equal OUT
  495.     MOV    [BX+COM_IN_PTR],SI
  496.     XOR    AX,AX
  497.     MOV    [BX+COM_IN_CTR],AX    ; zero the counter
  498.     TEST    [BX+COM_FLAGS],CF_ISF    ; input flow-controlled off?
  499.     JZ    CI1            ; if not...
  500.     CALL    RESTART_INPUT        ; send a Control-Q
  501.  
  502. CI1:    STI
  503.     RET
  504. F_CLEAR_INPUT ENDP
  505.  
  506. ; Start output
  507.  
  508. F_OUTPUT PROC NEAR ; BX -> port table, DX = i/o address, ES:DI -> buffer,
  509.            ; CX = buffer size in bytes
  510.     INC    DL            ; DX -> Interrupt Enable Register
  511.     CLI                ; interrupts off
  512.     IN    AL,DX
  513.     AND    AL,0FDH            ; reset transmit-interrupt enable
  514.     JMP    SHORT $+2
  515.     OUT    DX,AL
  516.     STI                ; interrupts on
  517.     ADD    DL,COM_LSR-COM_IER    ; DX -> Line Status Register
  518.  
  519. OUT1:    IN    AL,DX            ; wait until transmitter is empty
  520.     TEST    AL,40H
  521.     JZ    OUT1
  522.  
  523.     CLI                ; disallow interrupts
  524.     MOV    [BX+COM_OUT_PTR],DI    ; set output pointer
  525.     ADD    DI,CX            ; and limit pointer
  526.     MOV    [BX+COM_OUT_LIMIT],DI
  527.     MOV    AX,ES            ; buffer assumed to be in one segment
  528.     MOV    [BX+COM_OUT_SEG],AX
  529.  
  530.     TEST    [BX+COM_FLAGS],CF_OSF    ; output flow control on?
  531.     JNZ    OUT2            ; if so then a ^Q will start it
  532.     CALL    START_OUTPUT        ; else start the transmitter
  533.  
  534. OUT2:    STI                ; leave with interrupts on
  535.     RET
  536. F_OUTPUT ENDP
  537.  
  538. ; Check status of output in progress
  539.  
  540. F_CHECK_OUTPUT PROC NEAR    ; BX -> port table, DX = i/o address
  541.     MOV    AX,[BX+COM_OUT_PTR]    ; get output pointer
  542.     TEST    AX,AX
  543.     JZ    CHECK1            ; if zero, output is done
  544.     SUB    AX,[BX+COM_OUT_LIMIT]    ; else calculate number of characters
  545.     NEG    AX            ;    still to go
  546.     RET
  547.  
  548. CHECK1:    ADD    DX,COM_LSR - COM_TXB    ; DX -> Line Status Register
  549.     IN    AL,DX
  550.     AND    AL,20H            ; Tx Holding Register Empty?
  551.     JNZ    CHECK2
  552.     INC    AX            ; still 1 en route thru COM
  553.     RET
  554.  
  555. CHECK2:    XOR    AX,AX            ; only return 0 if ready for more
  556.     RET
  557. F_CHECK_OUTPUT ENDP
  558.  
  559. ; Abort output in progress
  560.  
  561. F_ABORT_OUTPUT PROC NEAR    ; BX -> port table
  562.     XOR    AX,AX
  563.     MOV    [BX+COM_OUT_PTR],AX
  564.     RET
  565. F_ABORT_OUTPUT ENDP
  566.  
  567. ; Single-character output
  568.  
  569. F_OUTCH    PROC    NEAR    ; BX -> port table, CL = character to be sent
  570.             ; DX = i/o address
  571. OC1:    ADD    DL,COM_LSR-COM_TXB    ; DX -> Line Status Register
  572.  
  573. OC2:    IN    AL,DX            ; wait until
  574.     TEST    AL,20H            ;    Transmitter Holding Register empty
  575.     JZ    OC2
  576.  
  577.     ADD    DL,COM_TXB-COM_LSR    ; DX -> Transmit Buffer
  578.     MOV    AL,CL
  579.     OUT    DX,AL            ; transmit it
  580.  
  581.     XOR    CX,CX
  582.     XCHG    [BX+COM_OUTFLOW],CL    ; flow-control character waiting?
  583.     TEST    CL,CL
  584.     JNZ    OC1            ; if so loop to send it
  585.     RET
  586. F_OUTCH    ENDP
  587.  
  588. ; Transmit a BREAK
  589.  
  590. F_BREAK    PROC    NEAR        ; BX -> port table, DX = i/o address
  591.     ADD    DL,COM_LCR        ; DX -> Line Control Register
  592.     CLI                ; interrupts off while changing LCR
  593.     IN    AL,DX
  594.     OR    AL,40H            ; set BREAK
  595.     JMP    SHORT $+2
  596.     OUT    DX,AL
  597.     STI
  598.     MOV    CL,7            ; send BREAK for approx 385 ms
  599.     CALL    DELAY
  600.     CLI
  601.     IN    AL,DX
  602.     XOR    AL,40H            ; reset BREAK
  603.     JMP    SHORT $+2
  604.     OUT    DX,AL
  605.     STI
  606.     RET
  607. F_BREAK    ENDP
  608.  
  609. ; (Unused)
  610.  
  611. F_STATUS PROC    NEAR    ; BX -> port table, DX = i/o address
  612.     RET            ; not sure we need this...
  613. F_STATUS ENDP
  614.  
  615. ; Set line speed
  616.  
  617. F_SPEED    PROC    NEAR    ; BX -> port table, DX = i/o address, DI = speed
  618.     MOV    AX,DI            ; AX = speed
  619.     CALL    SET_SPEED
  620.     RET
  621. F_SPEED    ENDP
  622.  
  623. ; Deconfigure a port
  624.  
  625. F_DECONFIG PROC    NEAR    ; BX -> port table, DX = i/o address
  626.     MOV    AH,[BX+COM_BIT]
  627.     CLI                ; mask off interrupt at controller
  628.     IN    AL,21H
  629.     OR    AL,AH
  630.     OUT    21H,AL
  631.     STI
  632.  
  633.     ADD    DL,COM_LCR        ; DX -> Line-Control Register
  634.     XOR    AX,AX            ; allow access to IER
  635.     OUT    DX,AL
  636.  
  637.     ADD    DL,COM_IER-COM_LCR    ; DX -> Interrupt Enable Register
  638.     JMP    $+2
  639.     OUT    DX,AL            ; turn off all interrupts
  640.  
  641.     ADD    DL,COM_MCR-COM_IER    ; DX -> Modem-Control Register
  642.     JMP    $+2
  643.     OUT    DX,AL            ; turn off OUT2 and all modem controls
  644.     RET
  645. F_DECONFIG ENDP
  646.  
  647. ; Wait for a given number of clock ticks
  648.  
  649. DELAY    PROC    NEAR    ; CL = number of 18.2-to-a-second ticks to delay
  650.     PUSH    DS
  651.     XOR    AX,AX
  652.     MOV    DS,AX            ; DS = segment 0
  653.     XOR    CH,CH
  654.  
  655. OS1:    CMP    AX,DS:[TIMER]        ; check AX against system clock
  656.     JE    OS1            ; if the same then wait
  657.     MOV    AX,DS:[TIMER]        ; else AX = value of system clock
  658.     LOOP    OS1            ; decrement tick counter
  659.  
  660.     POP    DS
  661.     RET
  662. DELAY    ENDP
  663.  
  664. ; Convert line speed from bits per second to divisor for COM port
  665.  
  666. GET_DIVISOR    PROC    NEAR    ; AX = speed in bits per second
  667.     OR    AX,AX            ; special case of zero speed
  668.     JNE    DIV1
  669.     INC    AX            ; really means 115,200 bps
  670.     RET                ; for which divisor is 1
  671.  
  672. DIV1:    PUSH    BX
  673.     CMP    AX,600            ; calculation break at 600 bps
  674.     JB    DIV2            ; if below 600
  675.     PUSH    DX
  676.     XOR    DX,DX
  677.     MOV    BX,100
  678.     DIV    BX            ; divide speed by 100
  679.     MOV    BX,AX
  680.     MOV    AX,1152
  681.     DIV    BX            ; then divide (speed/100) into 1152
  682.     POP    DX            ; that gives the desired result
  683.     POP    BX
  684.     RET
  685.  
  686. DIV2:    MOV    BL,10            ; divide the speed by 10
  687.     DIV    BL
  688.     MOV    BL,AL            ; BL = speed/10
  689.     MOV    AX,120
  690.     DIV    BL            ; divide that into 120
  691.     MOV    BL,96            ; BL = divisor for 1,200 bps
  692.     MUL    BL            ; this gives the desired result
  693.     POP    BX
  694.     RET
  695. GET_DIVISOR    ENDP        ; returns divisor in AX
  696.  
  697. ; Send a Control-Q to restart input
  698.  
  699. RESTART_INPUT    PROC    NEAR    ; BX -> port table, DX -> i/o address, Ints off
  700.     AND    [BX+COM_FLAGS],NOT CF_ISF    ; reset input-off flag
  701.     ADD    DL,COM_LSR        ; DX -> Line Status Register
  702.     IN    AL,DX
  703.     TEST    AL,20H            ; transmit buffer empty?
  704.     MOV    AL,CTRL_Q
  705.     JZ    RI1            ; no
  706.     ADD    DL,COM_TXB-COM_LSR    ; DX -> Transmit Buffer
  707.     OUT    DX,AL
  708.     XOR    AX,AX
  709.  
  710. RI1:    MOV    [BX+COM_OUTFLOW],AL
  711.     RET
  712. RESTART_INPUT    ENDP
  713.  
  714. ; Set the line speed for a port
  715.  
  716. SET_SPEED    PROC    NEAR    ; AX = speed in bps, DX = i/o address
  717.     PUSH    DX
  718.     CALL    GET_DIVISOR        ; convert speed to divisor
  719.     PUSH    AX
  720.     ADD    DL,COM_LCR        ; DX -> Line-Control Register
  721.     IN    AL,DX            ; AL = LCR setting
  722.     OR    AL,80H            ; set Divisor Latch Access bit
  723.     JMP    $+2
  724.     OUT    DX,AL
  725.     POP    AX            ; AX = divisor
  726.     ADD    DL,COM_DLL-COM_LCR
  727.     OUT    DX,AL
  728.     INC    DX            ; DX -> COM_DLM
  729.     MOV    AL,AH
  730.     OUT    DX,AL
  731.     ADD    DL,COM_LCR-COM_DLM    ; DX -> Line-Control Register
  732.     JMP    $+2
  733.     IN    AL,DX            ; read LCR again
  734.     AND    AL,07FH            ; clear DLA bit
  735.     JMP    $+2
  736.     OUT    DX,AL
  737.     POP    DX
  738.     RET
  739. SET_SPEED ENDP
  740.  
  741. ; Start the COM Port transmitting and prime for transmit interrupts
  742.  
  743. START_OUTPUT    PROC    NEAR    ; DX -> LSR, interrupts off
  744. PRIME:    ADD    DL,COM_IER-COM_LSR    ; DX -> Interrupt Enable Register
  745.     IN    AL,DX
  746.     AND    AL,0FDh            ; disable transmit interrupts
  747.     JMP    $+2
  748.     OUT    DX,AL
  749.     INC    DX            ; DX -> Interrupt Identification Reg
  750.     CALL    TX_INT            ; dispatch the next character
  751.     JC    EMPTY            ; if there was nothing to transmit
  752.     ADD    DL,COM_IER-COM_TXB    ; DX -> Interrupt Enable Register
  753.     IN    AL,DX
  754.     OR    AL,2            ; enable transmit interrupts
  755.     JMP    $+2
  756.     OUT    DX,AL
  757.     ADD    DL,COM_LSR-COM_IER
  758.     IN    AL,DX
  759.     AND    AL,20H            ; transmitter buffer still empty?
  760.     JNZ    PRIME            ; if so prime it again
  761.  
  762. EMPTY:    RET
  763. START_OUTPUT    ENDP
  764.  
  765. THE_END    =    $            ; end of resident code
  766.  
  767. ; Startup
  768.  
  769. MAIN    PROC    NEAR
  770.     MOV    AH,80H            ; check if Couriers is already loaded
  771.     INT    14H
  772.     CMP    AH,232            ; if it is it will return AH = 232
  773.     JNE    LOAD
  774.     LEA    DX,BADMSG        ; complain
  775.     MOV    AH,9H
  776.     INT    21H
  777.     XOR    AX,AX            ; Couriers already loaded so exit
  778.     INT    21H
  779.  
  780. LOAD:    MOV    AX,DS:[44]        ; release copy of environment
  781.     MOV    ES,AX
  782.     MOV    AH,49H
  783.     INT    21H
  784.     LEA    DX,INITMSG        ; announce program
  785.     MOV    AH,9H
  786.     INT    21H
  787.     MOV    AX,3514H        ; get BIOS serial-port services
  788.     INT    21H            ;    interrupt vector
  789.     MOV    WORD PTR [EXINT14],BX    ; save offset
  790.     MOV    WORD PTR [EXINT14+2],ES    ; and segment
  791.     MOV    AX,2514H        ; change vector for intercept
  792.     LEA    DX,INTERCEPT14
  793.     INT    21H
  794.     MOV    DX,OFFSET THE_END + 15    ; calculate paragraphs used
  795.     MOV    CL,4
  796.     SHR    DX,CL
  797.     MOV    AX,3100H        ; terminate but stay resident
  798.     INT    21H
  799.  
  800. MAIN    ENDP
  801.  
  802. BADMSG    DB    'Couriers is already loaded$'
  803.  
  804. PSEG    ENDS
  805.     END    ENTRY
  806.